diff options
45 files changed, 1561 insertions, 447 deletions
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 02cabc935e..910457d3b3 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -9,7 +9,8 @@ git-am - Apply a series of patches in a mailbox SYNOPSIS -------- [verse] -'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>... +'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] + [--interactive] [--whitespace=<option>] <mbox>... 'git-am' [--skip | --resolved] DESCRIPTION @@ -46,6 +47,10 @@ OPTIONS Skip the current patch. This is only meaningful when restarting an aborted patch. +--whitespace=<option>:: + This flag is passed to the `git-apply` program that applies + the patch. + --interactive:: Run interactively, just like git-applymbox. @@ -80,7 +85,7 @@ names. SEE ALSO -------- -gitlink:git-applymbox[1], gitlink:git-applypatch[1]. +gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1]. Author diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 75076b6121..1c64a1aa82 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] + [--whitespace=<nowarn|warn|error|error-all|strip>] [<patch>...] DESCRIPTION @@ -97,6 +98,35 @@ OPTIONS result. This allows binary files to be patched in a very limited way. +--whitespace=<option>:: + When applying a patch, detect a new or modified line + that ends with trailing whitespaces (this includes a + line that solely consists of whitespaces). By default, + the command outputs warning messages and applies the + patch. + When `git-apply` is used for statistics and not applying a + patch, it defaults to `nowarn`. + You can use different `<option>` to control this + behaviour: ++ +* `nowarn` turns off the trailing whitespace warning. +* `warn` outputs warnings for a few such errors, but applies the + patch (default). +* `error` outputs warnings for a few such errors, and refuses + to apply the patch. +* `error-all` is similar to `error` but shows all errors. +* `strip` outputs warnings for a few such errors, strips out the + trailing whitespaces and applies the patch. + + +Configuration +------------- + +apply.whitespace:: + When no `--whitespace` flag is given from the command + line, this configuration item is used as the default. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index 2a1e526c6a..b0b65889ac 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -10,7 +10,9 @@ SYNOPSIS -------- [verse] 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] - [--stage=<number>] [--] <file>... + [--stage=<number>] + [-z] [--stdin] + [--] [<file>]\* DESCRIPTION ----------- @@ -45,6 +47,15 @@ OPTIONS Instead of checking out unmerged entries, copy out the files from named stage. <number> must be between 1 and 3. +--stdin:: + Instead of taking list of paths from the command line, + read list of paths from the standard input. Paths are + separated by LF (i.e. one path per line) by default. + +-z:: + Only meaningful with `--stdin`; paths are separated with + NUL character instead of LF. + --:: Do not interpret any more arguments as options. @@ -64,7 +75,12 @@ $ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f -- which will force all existing `*.h` files to be replaced with their cached copies. If an empty command line implied "all", then this would -force-refresh everything in the index, which was not the point. +force-refresh everything in the index, which was not the point. But +since git-checkout-index accepts --stdin it would be faster to use: + +---------------- +$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin +---------------- The `--` is just a good idea when you know the rest will be filenames; it will prevent problems with a filename of, for example, `-a`. diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 88f07ff15d..19c9c51cff 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -54,6 +54,30 @@ INSTALLATION of branches in git). $ cvs co -d mylocaldir master +Eclipse CVS Client Notes +------------------------ + +To get a checkout with the Eclipse CVS client: + +1. Create a new project from CVS checkout, giving it repository and module +2. Context Menu->Team->Share Project... +3. Enter the repository and module information again and click Finish +4. The Synchronize view appears. Untick "launch commit wizard" to avoid +committing the .project file, and select HEAD as the tag to synchronize to. +Update all incoming changes. + +Note that most versions of Eclipse ignore CVS_SERVER (which you can set in +the Preferences->Team->CVS->ExtConnection pane), so you may have to +rename, alias or symlink git-cvsserver to 'cvs' on the server. + +Clients known to work +--------------------- + +CVS 1.12.9 on Debian +CVS 1.11.17 on MacOSX (from Fink package) +Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) +TortoiseCVS + Operations supported -------------------- diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 6fbd6d9368..844cfda8d2 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' (<tree-ish> | [[-m | --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -50,6 +50,19 @@ OPTIONS trees that are not directly related to the current working tree status into a temporary index file. +--aggressive:: + Usually a three-way merge by `git-read-tree` resolves + the merge for really trivial cases and leaves other + cases unresolved in the index, so that Porcelains can + implement different merge policies. This flag makes the + command to resolve a few more cases internally: ++ +* when one side removes a path and the other side leaves the path + unmodified. The resolution is to remove that path. +* when both sides remove a path. The resolution is to remove that path. +* when both sides adds a path identically. The resolution + is to add that path. + <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 5b306d6593..8255ae1bce 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -18,7 +18,7 @@ SYNOPSIS [ \--all ] [ \--topo-order ] [ \--parents ] - [ \--objects [ \--unpacked ] ] + [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] <commit>... [ \-- <paths>... ] @@ -53,6 +53,14 @@ OPTIONS which I need to download if I have the commit object 'bar', but not 'foo'". +--objects-edge:: + Similar to `--objects`, but also print the IDs of + excluded commits refixed with a `-` character. This is + used by `git-pack-objects` to build 'thin' pack, which + records objects in deltified form based on objects + contained in these excluded commits to reduce network + traffic. + --unpacked:: Only useful with `--objects`; print the object IDs that are not in packs. diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index 5c543d5d1b..a1588138ea 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -10,10 +10,11 @@ git-svnimport - Import a SVN repository into git SYNOPSIS -------- 'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ] - [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev] - [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] - [ -s start_chg ] [ -m ] [ -M regex ] - <SVN_repository_URL> [ <path> ] + [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev] + [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] + [ -s start_chg ] [ -m ] [ -r ] [ -M regex ] + [ -I <ignorefile_name> ] [ -A <author_file> ] + <SVN_repository_URL> [ <path> ] DESCRIPTION @@ -65,6 +66,27 @@ When importing incrementally, you might need to edit the .git/svn2git file. Prepend 'rX: ' to commit messages, where X is the imported subversion revision. +-I <ignorefile_name>:: + Import the svn:ignore directory property to files with this + name in each directory. (The Subversion and GIT ignore + syntaxes are similar enough that using the Subversion patterns + directly with "-I .gitignore" will almost always just work.) + +-A <author_file>:: + Read a file with lines on the form + + username = User's Full Name <email@addr.es> + + and use "User's Full Name <email@addr.es>" as the GIT + author and committer for Subversion commits made by + "username". If encountering a commit made by a user not in the + list, abort. + + For convenience, this data is saved to $GIT_DIR/svn-authors + each time the -A option is provided, and read from that same + file each time git-svnimport is run with an existing GIT + repository without -A. + -m:: Attempt to detect merges based on the commit message. This option will enable default regexes that try to capture the name source diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1056b7c817..d6d1ae0338 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -7,8 +7,11 @@ DEF_VER=v1.2.GIT # (included in release tarballs), then default if VN=$(git-describe --abbrev=4 HEAD 2>/dev/null); then VN=$(echo "$VN" | sed -e 's/-/./g'); -else +elif test -f version +then VN=$(cat version) || VN="$DEF_VER" +else + VN="$DEF_VER" fi VN=$(expr "$VN" : v*'\(.*\)') @@ -196,7 +196,8 @@ LIB_H = \ DIFF_OBJS = \ diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \ - diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o + diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \ + diffcore-delta.o LIB_OBJS = \ blob.o commit.o connect.o count-delta.o csum-file.o \ @@ -223,11 +224,15 @@ ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease ## fink - ALL_CFLAGS += -I/sw/include - ALL_LDFLAGS += -L/sw/lib + ifeq ($(shell test -d /sw/lib && echo y),y) + ALL_CFLAGS += -I/sw/include + ALL_LDFLAGS += -L/sw/lib + endif ## darwinports - ALL_CFLAGS += -I/opt/local/include - ALL_LDFLAGS += -L/opt/local/lib + ifeq ($(shell test -d /opt/local/lib && echo y),y) + ALL_CFLAGS += -I/opt/local/include + ALL_LDFLAGS += -L/opt/local/lib + endif endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -32,7 +32,57 @@ static int no_add = 0; static int show_index_info = 0; static int line_termination = '\n'; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; + +static enum whitespace_eol { + nowarn_whitespace, + warn_on_whitespace, + error_on_whitespace, + strip_whitespace, +} new_whitespace = warn_on_whitespace; +static int whitespace_error = 0; +static int squelch_whitespace_errors = 5; +static int applied_after_stripping = 0; +static const char *patch_input_file = NULL; + +static void parse_whitespace_option(const char *option) +{ + if (!option) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "warn")) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "nowarn")) { + new_whitespace = nowarn_whitespace; + return; + } + if (!strcmp(option, "error")) { + new_whitespace = error_on_whitespace; + return; + } + if (!strcmp(option, "error-all")) { + new_whitespace = error_on_whitespace; + squelch_whitespace_errors = 0; + return; + } + if (!strcmp(option, "strip")) { + new_whitespace = strip_whitespace; + return; + } + die("unrecognized whitespace option '%s'", option); +} + +static void set_default_whitespace_mode(const char *whitespace_option) +{ + if (!whitespace_option && !apply_default_whitespace) { + new_whitespace = (apply + ? warn_on_whitespace + : nowarn_whitespace); + } +} /* * For "diff-stat" like behaviour, we keep track of the biggest change @@ -815,6 +865,25 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s oldlines--; break; case '+': + /* + * We know len is at least two, since we have a '+' and + * we checked that the last character was a '\n' above. + * That is, an addition of an empty line would check + * the '+' here. Sneaky... + */ + if ((new_whitespace != nowarn_whitespace) && + isspace(line[len-2])) { + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < + whitespace_error) + ; + else { + fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", + patch_input_file, + linenr, len-2, line+1); + } + } added++; newlines--; break; @@ -1092,6 +1161,28 @@ struct buffer_desc { unsigned long alloc; }; +static int apply_line(char *output, const char *patch, int plen) +{ + /* plen is number of bytes to be copied from patch, + * starting at patch+1 (patch[0] is '+'). Typically + * patch[plen] is '\n'. + */ + int add_nl_to_tail = 0; + if ((new_whitespace == strip_whitespace) && + 1 < plen && isspace(patch[plen-1])) { + if (patch[plen] == '\n') + add_nl_to_tail = 1; + plen--; + while (0 < plen && isspace(patch[plen])) + plen--; + applied_after_stripping++; + } + memcpy(output, patch + 1, plen); + if (add_nl_to_tail) + output[plen++] = '\n'; + return plen; +} + static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) { char *buf = desc->buffer; @@ -1127,10 +1218,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) break; /* Fall-through for ' ' */ case '+': - if (*patch != '+' || !no_add) { - memcpy(new + newsize, patch + 1, plen); - newsize += plen; - } + if (*patch != '+' || !no_add) + newsize += apply_line(new + newsize, patch, + plen); break; case '@': case '\\': /* Ignore it, we already handled it */ @@ -1699,7 +1789,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd) +static int apply_patch(int fd, const char *filename) { int newfd; unsigned long offset, size; @@ -1707,6 +1797,7 @@ static int apply_patch(int fd) struct patch *list = NULL, **listp = &list; int skipped_patch = 0; + patch_input_file = filename; if (!buffer) return -1; offset = 0; @@ -1733,6 +1824,9 @@ static int apply_patch(int fd) } newfd = -1; + if (whitespace_error && (new_whitespace == error_on_whitespace)) + apply = 0; + write_index = check_index && apply; if (write_index) newfd = hold_index_file_for_update(&cache_file, get_index_file()); @@ -1769,17 +1863,28 @@ static int apply_patch(int fd) return 0; } +static int git_apply_config(const char *var, const char *value) +{ + if (!strcmp(var, "apply.whitespace")) { + apply_default_whitespace = strdup(value); + return 0; + } + return git_default_config(var, value); +} + + int main(int argc, char **argv) { int i; int read_stdin = 1; + const char *whitespace_option = NULL; for (i = 1; i < argc; i++) { const char *arg = argv[i]; int fd; if (!strcmp(arg, "-")) { - apply_patch(0); + apply_patch(0, "<stdin>"); read_stdin = 0; continue; } @@ -1839,11 +1944,18 @@ int main(int argc, char **argv) line_termination = 0; continue; } + if (!strncmp(arg, "--whitespace=", 13)) { + whitespace_option = arg + 13; + parse_whitespace_option(arg + 13); + continue; + } if (check_index && prefix_length < 0) { prefix = setup_git_directory(); prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_default_config); + git_config(git_apply_config); + if (!whitespace_option && apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); } if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); @@ -1852,10 +1964,38 @@ int main(int argc, char **argv) if (fd < 0) usage(apply_usage); read_stdin = 0; - apply_patch(fd); + set_default_whitespace_mode(whitespace_option); + apply_patch(fd, arg); close(fd); } + set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0); + apply_patch(0, "<stdin>"); + 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", + squelched, + squelched == 1 ? "" : "s"); + } + if (new_whitespace == error_on_whitespace) + die("%d line%s add%s trailing whitespaces.", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + if (applied_after_stripping) + fprintf(stderr, "warning: %d line%s applied after" + " stripping trailing whitespaces.\n", + applied_after_stripping, + applied_after_stripping == 1 ? "" : "s"); + else if (whitespace_error) + fprintf(stderr, "warning: %d line%s add%s trailing" + " whitespaces.\n", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + } return 0; } @@ -161,11 +161,13 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path); extern int commit_index_file(struct cache_file *); extern void rollback_index_file(struct cache_file *); +/* Environment bits from configuration mechanism */ extern int trust_executable_bit; extern int assume_unchanged; extern int only_use_symrefs; extern int diff_rename_limit_default; extern int shared_repository; +extern const char *apply_default_whitespace; #define GIT_REPO_VERSION 0 extern int repository_format_version; diff --git a/cat-file.c b/cat-file.c index 96d66b4304..1a613f3ee5 100644 --- a/cat-file.c +++ b/cat-file.c @@ -4,6 +4,92 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "exec_cmd.h" + +static void flush_buffer(const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(1, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("git-cat-file: %s", strerror(errno)); + } else if (!ret) { + die("git-cat-file: disk full?"); + } + size -= ret; + buf += ret; + } +} + +static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +{ + /* the parser in tag.c is useless here. */ + const char *endp = buf + size; + const char *cp = buf; + + while (cp < endp) { + char c = *cp++; + if (c != '\n') + continue; + if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) { + const char *tagger = cp; + + /* Found the tagger line. Copy out the contents + * of the buffer so far. + */ + flush_buffer(buf, cp - buf); + + /* + * Do something intelligent, like pretty-printing + * the date. + */ + while (cp < endp) { + if (*cp++ == '\n') { + /* tagger to cp is a line + * that has ident and time. + */ + const char *sp = tagger; + char *ep; + unsigned long date; + long tz; + while (sp < cp && *sp != '>') + sp++; + if (sp == cp) { + /* give up */ + flush_buffer(tagger, + cp - tagger); + break; + } + while (sp < cp && + !('0' <= *sp && *sp <= '9')) + sp++; + flush_buffer(tagger, sp - tagger); + date = strtoul(sp, &ep, 10); + tz = strtol(ep, NULL, 10); + sp = show_date(date, tz); + flush_buffer(sp, strlen(sp)); + xwrite(1, "\n", 1); + break; + } + } + break; + } + if (cp < endp && *cp == '\n') + /* end of header */ + break; + } + /* At this point, we have copied out the header up to the end of + * the tagger line and cp points at one past \n. It could be the + * next header line after the tagger line, or it could be another + * \n that marks the end of the headers. We need to copy out the + * remainder as is. + */ + if (cp < endp) + flush_buffer(cp, endp - cp); + return 0; +} int main(int argc, char **argv) { @@ -15,7 +101,7 @@ int main(int argc, char **argv) setup_git_directory(); if (argc != 3 || get_sha1(argv[2], sha1)) - usage("git-cat-file [-t|-s|-e|<type>] <sha1>"); + usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>"); opt = 0; if ( argv[1][0] == '-' ) { @@ -43,6 +129,23 @@ int main(int argc, char **argv) case 'e': return !has_sha1_file(sha1); + case 'p': + if (get_sha1(argv[2], sha1) || + sha1_object_info(sha1, type, NULL)) + die("Not a valid object name %s", argv[2]); + + /* custom pretty-print here */ + if (!strcmp(type, "tree")) + return execl_git_cmd("ls-tree", argv[2], NULL); + + buf = read_sha1_file(sha1, type, &size); + if (!buf) + die("Cannot read object %s", argv[2]); + if (!strcmp(type, "tag")) + return pprint_tag(sha1, buf, size); + + /* otherwise just spit out the data */ + break; case 0: buf = read_object_with_reference(sha1, argv[1], &size, NULL); break; @@ -54,18 +157,6 @@ int main(int argc, char **argv) if (!buf) die("git-cat-file %s: bad file", argv[2]); - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("git-cat-file: %s", strerror(errno)); - } else if (!ret) { - die("git-cat-file: disk full?"); - } - size -= ret; - buf += ret; - } + flush_buffer(buf, size); return 0; } diff --git a/checkout-index.c b/checkout-index.c index 957b4a86b0..f54c606414 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -22,6 +22,10 @@ * * find . -name '*.h' -print0 | xargs -0 git-checkout-index -f -- * + * or: + * + * find . -name '*.h' -print0 | git-checkout-index -f -z --stdin + * * which will force all existing *.h files to be replaced with * their cached copies. If an empty command line implied "all", * then this would force-refresh everything in the cache, which @@ -33,6 +37,8 @@ * but get used to it in scripting!). */ #include "cache.h" +#include "strbuf.h" +#include "quote.h" static const char *prefix; static int prefix_length; @@ -114,6 +120,8 @@ int main(int argc, char **argv) int i; int newfd = -1; int all = 0; + int read_from_stdin = 0; + int line_termination = '\n'; prefix = setup_git_directory(); git_config(git_default_config); @@ -156,6 +164,17 @@ int main(int argc, char **argv) die("cannot open index.lock file."); continue; } + if (!strcmp(arg, "-z")) { + line_termination = 0; + continue; + } + if (!strcmp(arg, "--stdin")) { + if (i != argc - 1) + die("--stdin must be at the end"); + read_from_stdin = 1; + i++; /* do not consider arg as a file name */ + break; + } if (!strncmp(arg, "--prefix=", 9)) { state.base_dir = arg+9; state.base_dir_len = strlen(state.base_dir); @@ -191,9 +210,31 @@ int main(int argc, char **argv) if (all) die("git-checkout-index: don't mix '--all' and explicit filenames"); + if (read_from_stdin) + die("git-checkout-index: don't mix '--stdin' and explicit filenames"); checkout_file(prefix_path(prefix, prefix_length, arg)); } + if (read_from_stdin) { + struct strbuf buf; + if (all) + die("git-checkout-index: don't mix '--all' and '--stdin'"); + strbuf_init(&buf); + while (1) { + char *path_name; + read_line(&buf, stdin, line_termination); + if (buf.eof) + break; + if (line_termination && buf.buf[0] == '"') + path_name = unquote_c_style(buf.buf, NULL); + else + path_name = buf.buf; + checkout_file(prefix_path(prefix, prefix_length, path_name)); + if (path_name != buf.buf) + free(path_name); + } + } + if (all) checkout_all(); diff --git a/combine-diff.c b/combine-diff.c index d812600d11..a23894d869 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -621,7 +621,8 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt, } static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, const char *header) + int dense, const char *header, + struct diff_options *opt) { unsigned long size, cnt, lno; char *result, *cp, *ep; @@ -631,6 +632,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, char ourtmp_buf[TMPPATHLEN]; char *ourtmp = ourtmp_buf; int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); + int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; /* Read the result of merge first */ if (!working_tree_file) { @@ -724,7 +726,7 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, if (header) { shown_header++; - puts(header); + printf("%s%c", header, opt->line_termination); } printf("diff --%s ", dense ? "cc" : "combined"); if (quote_c_style(elem->path, NULL, NULL, 0)) @@ -735,10 +737,10 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, printf("index "); for (i = 0; i < num_parent; i++) { abb = find_unique_abbrev(elem->parent[i].sha1, - DEFAULT_ABBREV); + abbrev); printf("%s%s", i ? "," : "", abb); } - abb = find_unique_abbrev(elem->sha1, DEFAULT_ABBREV); + abb = find_unique_abbrev(elem->sha1, abbrev); printf("..%s\n", abb); if (mode_differs) { @@ -797,7 +799,7 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha inter_name_termination = 0; if (header) - puts(header); + printf("%s%c", header, line_termination); for (i = 0; i < num_parent; i++) { if (p->parent[i].mode) @@ -862,7 +864,7 @@ int show_combined_diff(struct combine_diff_path *p, default: case DIFF_FORMAT_PATCH: - return show_patch_diff(p, num_parent, dense, header); + return show_patch_diff(p, num_parent, dense, header, opt); } } diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index 0b7416526d..0e092c5d3f 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -34,13 +34,14 @@ use POSIX qw/strftime/; my $sha1 = qr/[a-f\d]{40}/; my $sha1_short = qr/[a-f\d]{6,40}/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_version); + $_find_copies_harder, $_l, $_version, $_upgrade); GetOptions( 'revision|r=s' => \$_revision, 'no-ignore-externals' => \$_no_ignore_ext, 'stdin|' => \$_stdin, 'edit|e' => \$_edit, 'rmdir' => \$_rmdir, + 'upgrade' => \$_upgrade, 'help|H|h' => \$_help, 'find-copies-harder' => \$_find_copies_harder, 'l=i' => \$_l, @@ -106,13 +107,18 @@ sub rebuild { $SVN_URL = shift or undef; my $repo_uuid; my $newest_rev = 0; + if ($_upgrade) { + sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD"); + } else { + check_upgrade_needed(); + } my $pid = open(my $rev_list,'-|'); defined $pid or croak $!; if ($pid == 0) { - exec("git-rev-list","$GIT_SVN-HEAD") or croak $!; + exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!; } - my $first; + my $latest; while (<$rev_list>) { chomp; my $c = $_; @@ -132,18 +138,20 @@ sub rebuild { "$c, $id\n"; } } + + # if we merged or otherwise started elsewhere, this is + # how we break out of it + next if (defined $repo_uuid && ($uuid ne $repo_uuid)); + next if (defined $SVN_URL && ($url ne $SVN_URL)); + print "r$rev = $c\n"; - unless (defined $first) { + unless (defined $latest) { if (!$SVN_URL && !$url) { croak "SVN repository location required: $url\n"; } $SVN_URL ||= $url; - $repo_uuid = setup_git_svn(); - $first = $rev; - } - if ($uuid ne $repo_uuid) { - croak "Repository UUIDs do not match!\ngot: $uuid\n", - "expected: $repo_uuid\n"; + $repo_uuid ||= setup_git_svn(); + $latest = $rev; } assert_revision_eq_or_unknown($rev, $c); sys('git-update-ref',"$GIT_SVN/revs/$rev",$c); @@ -151,7 +159,7 @@ sub rebuild { } close $rev_list or croak $?; if (!chdir $SVN_WC) { - my @svn_co = ('svn','co',"-r$first"); + my @svn_co = ('svn','co',"-r$latest"); push @svn_co, '--ignore-externals' unless $_no_ignore_ext; sys(@svn_co, $SVN_URL, $SVN_WC); chdir $SVN_WC or croak $!; @@ -168,6 +176,13 @@ sub rebuild { exec('git-write-tree'); } waitpid $pid, 0; + + if ($_upgrade) { + print STDERR <<""; +Keeping deprecated refs/head/$GIT_SVN-HEAD for now. Please remove it +when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN + + } } sub init { @@ -180,6 +195,7 @@ sub init { sub fetch { my (@parents) = @_; + check_upgrade_needed(); $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url"); my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL); unless ($_revision) { @@ -222,6 +238,7 @@ sub fetch { sub commit { my (@commits) = @_; + check_upgrade_needed(); if ($_stdin || !@commits) { print "Reading from stdin...\n"; @commits = (); @@ -863,7 +880,7 @@ sub git_commit { if ($commit !~ /^$sha1$/o) { croak "Failed to commit, invalid sha1: $commit\n"; } - my @update_ref = ('git-update-ref',"refs/heads/$GIT_SVN-HEAD",$commit); + my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit); if (my $primary_parent = shift @exec_parents) { push @update_ref, $primary_parent; } @@ -936,6 +953,28 @@ sub svn_check_ignore_externals { $_no_ignore_ext = 1; } } + +sub check_upgrade_needed { + my $old = eval { + my $pid = open my $child, '-|'; + defined $pid or croak $!; + if ($pid == 0) { + close STDERR; + exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $?; + } + my @ret = (<$child>); + close $child or croak $?; + die $? if $?; # just in case close didn't error out + return wantarray ? @ret : join('',@ret); + }; + return unless $old; + my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") }; + if ($@ || !$head) { + print STDERR "Please run: $0 rebuild --upgrade\n"; + exit 1; + } +} + __END__ Data structures: diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt index b4b7789dee..4102deb381 100644 --- a/contrib/git-svn/git-svn.txt +++ b/contrib/git-svn/git-svn.txt @@ -41,7 +41,12 @@ init:: fetch:: Fetch unfetched revisions from the SVN_URL we are tracking. - refs/heads/git-svn-HEAD will be updated to the latest revision. + refs/heads/remotes/git-svn will be updated to the latest revision. + + Note: You should never attempt to modify the remotes/git-svn branch + outside of git-svn. Instead, create a branch from remotes/git-svn + and work on that branch. Use the 'commit' command (see below) + to write git commits back to remotes/git-svn. commit:: Commit specified commit or tree objects to SVN. This relies on @@ -150,13 +155,13 @@ Tracking and contributing to an Subversion managed-project: # Fetch remote revisions:: git-svn fetch # Create your own branch to hack on:: - git checkout -b my-branch git-svn-HEAD + git checkout -b my-branch remotes/git-svn # Commit only the git commits you want to SVN:: git-svn commit <tree-ish> [<tree-ish_2> ...] # Commit all the git commits from my-branch that don't exist in SVN:: - git commit git-svn-HEAD..my-branch + git-svn commit remotes/git-svn..my-branch # Something is committed to SVN, pull the latest into your branch:: - git-svn fetch && git pull . git-svn-HEAD + git-svn fetch && git pull . remotes/git-svn # Append svn:ignore settings to the default git exclude file: git-svn show-ignore >> .git/info/exclude @@ -179,7 +184,9 @@ SVN repositories via one git repository. Simply set the GIT_SVN_ID environment variable to a name other other than "git-svn" (the default) and git-svn will ignore the contents of the $GIT_DIR/git-svn directory and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that -invocation. +invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of +remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified +by the user outside of git-svn commands. ADDITIONAL FETCH ARGUMENTS -------------------------- diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh index 181dfe008b..80ad3573db 100644 --- a/contrib/git-svn/t/t0000-contrib-git-svn.sh +++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh @@ -71,14 +71,14 @@ test_expect_success \ name='try a deep --rmdir with a commit' -git checkout -b mybranch git-svn-HEAD +git checkout -b mybranch remotes/git-svn mv dir/a/b/c/d/e/file dir/file cp dir/file file git update-index --add --remove dir/a/b/c/d/e/file dir/file file git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch && test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a" @@ -91,13 +91,13 @@ git update-index --add dir/file/file git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \ || true name='detect node change from directory to file #1' rm -rf dir $GIT_DIR/index -git checkout -b mybranch2 git-svn-HEAD +git checkout -b mybranch2 remotes/git-svn mv bar/zzz zzz rm -rf bar mv zzz bar @@ -106,13 +106,13 @@ git update-index --add -- bar git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch2' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \ || true name='detect node change from file to directory #2' rm -f $GIT_DIR/index -git checkout -b mybranch3 git-svn-HEAD +git checkout -b mybranch3 remotes/git-svn rm bar/zzz git-update-index --remove bar/zzz mkdir bar/zzz @@ -121,13 +121,13 @@ git-update-index --add bar/zzz/yyy git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch3' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \ || true name='detect node change from directory to file #2' rm -f $GIT_DIR/index -git checkout -b mybranch4 git-svn-HEAD +git checkout -b mybranch4 remotes/git-svn rm -rf dir git update-index --remove -- dir/file touch dir @@ -136,19 +136,19 @@ git update-index --add -- dir git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch4' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \ || true name='remove executable bit from a file' rm -f $GIT_DIR/index -git checkout -b mybranch5 git-svn-HEAD +git checkout -b mybranch5 remotes/git-svn chmod -x exec.sh git update-index exec.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test ! -x $SVN_TREE/exec.sh" @@ -158,7 +158,7 @@ git update-index exec.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -x $SVN_TREE/exec.sh" @@ -170,7 +170,7 @@ git update-index exec.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -L $SVN_TREE/exec.sh" @@ -182,7 +182,7 @@ git update-index --add bar/zzz exec-2.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -x $SVN_TREE/bar/zzz && test -L $SVN_TREE/exec-2.sh" @@ -196,7 +196,7 @@ git update-index exec-2.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -f $SVN_TREE/exec-2.sh && test ! -L $SVN_TREE/exec-2.sh && diff -u help $SVN_TREE/exec-2.sh" @@ -207,9 +207,9 @@ name='test fetch functionality (svn => git) with alternate GIT_SVN_ID' GIT_SVN_ID=alt export GIT_SVN_ID test_expect_success "$name" \ - "git-svn init $svnrepo && git-svn fetch -v && - git-rev-list --pretty=raw git-svn-HEAD | grep ^tree | uniq > a && - git-rev-list --pretty=raw alt-HEAD | grep ^tree | uniq > b && + "git-svn init $svnrepo && git-svn fetch && + git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a && + git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b && diff -u a b" test_done diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview index 4e3847d8bf..de9f3f3c72 100755 --- a/contrib/gitview/gitview +++ b/contrib/gitview/gitview @@ -162,7 +162,7 @@ class CellRendererGraph(gtk.GenericCellRenderer): for item in names: names_len += len(item) - width = box_size * (cols + 1 ) + names_len + width = box_size * (cols + 1 ) + names_len height = box_size # FIXME I have no idea how to use cell_area properly @@ -239,20 +239,23 @@ class CellRendererGraph(gtk.GenericCellRenderer): box_size / 4, 0, 2 * math.pi) + self.set_colour(ctx, colour, 0.0, 0.5) + ctx.stroke_preserve() + + self.set_colour(ctx, colour, 0.5, 1.0) + ctx.fill_preserve() + if (len(names) != 0): name = " " for item in names: name = name + item + " " - ctx.select_font_face("Monospace") ctx.set_font_size(13) - ctx.text_path(name) - - self.set_colour(ctx, colour, 0.0, 0.5) - ctx.stroke_preserve() - - self.set_colour(ctx, colour, 0.5, 1.0) - ctx.fill() + if (flags & 1): + self.set_colour(ctx, colour, 0.5, 1.0) + else: + self.set_colour(ctx, colour, 0.0, 0.5) + ctx.show_text(name) class Commit: """ This represent a commit object obtained after parsing the git-rev-list @@ -261,11 +264,11 @@ class Commit: children_sha1 = {} def __init__(self, commit_lines): - self.message = "" + self.message = "" self.author = "" - self.date = "" - self.committer = "" - self.commit_date = "" + self.date = "" + self.committer = "" + self.commit_date = "" self.commit_sha1 = "" self.parent_sha1 = [ ] self.parse_commit(commit_lines) @@ -365,7 +368,7 @@ class DiffWindow: save_menu.connect("activate", self.save_menu_response, "save") save_menu.show() menu_bar.append(save_menu) - vbox.pack_start(menu_bar, False, False, 2) + vbox.pack_start(menu_bar, expand=False, fill=True) menu_bar.show() scrollwin = gtk.ScrolledWindow() @@ -391,7 +394,7 @@ class DiffWindow: sourceview.show() - def set_diff(self, commit_sha1, parent_sha1): + def set_diff(self, commit_sha1, parent_sha1, encoding): """Set the differences showed by this window. Compares the two trees and populates the window with the differences. @@ -401,7 +404,7 @@ class DiffWindow: return fp = os.popen("git diff-tree -p " + parent_sha1 + " " + commit_sha1) - self.buffer.set_text(fp.read()) + self.buffer.set_text(unicode(fp.read(), encoding).encode('utf-8')) fp.close() self.window.show() @@ -426,10 +429,11 @@ class GitView: def __init__(self, with_diff=0): self.with_diff = with_diff - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) + self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_border_width(0) self.window.set_title("Git repository browser") + self.get_encoding() self.get_bt_sha1() # Use three-quarters of the screen by default @@ -468,22 +472,20 @@ class GitView: self.bt_sha1[sha1].append(name) fp.close() + def get_encoding(self): + fp = os.popen("git repo-config --get i18n.commitencoding") + self.encoding=string.strip(fp.readline()) + fp.close() + if (self.encoding == ""): + self.encoding = "utf-8" + def construct(self): """Construct the window contents.""" + vbox = gtk.VBox() paned = gtk.VPaned() paned.pack1(self.construct_top(), resize=False, shrink=True) paned.pack2(self.construct_bottom(), resize=False, shrink=True) - self.window.add(paned) - paned.show() - - - def construct_top(self): - """Construct the top-half of the window.""" - vbox = gtk.VBox(spacing=6) - vbox.set_border_width(12) - vbox.show() - menu_bar = gtk.MenuBar() menu_bar.set_pack_direction(gtk.PACK_DIRECTION_RTL) help_menu = gtk.MenuItem("Help") @@ -495,11 +497,23 @@ class GitView: help_menu.set_submenu(menu) help_menu.show() menu_bar.append(help_menu) - vbox.pack_start(menu_bar, False, False, 2) menu_bar.show() + vbox.pack_start(menu_bar, expand=False, fill=True) + vbox.pack_start(paned, expand=True, fill=True) + self.window.add(vbox) + paned.show() + vbox.show() + + + def construct_top(self): + """Construct the top-half of the window.""" + vbox = gtk.VBox(spacing=6) + vbox.set_border_width(12) + vbox.show() + scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrollwin.set_shadow_type(gtk.SHADOW_IN) vbox.pack_start(scrollwin, expand=True, fill=True) scrollwin.show() @@ -683,7 +697,7 @@ class GitView: self.revid_label.set_text(revid_label) self.committer_label.set_text(committer) self.timestamp_label.set_text(timestamp) - self.message_buffer.set_text(message) + self.message_buffer.set_text(unicode(message, self.encoding).encode('utf-8')) for widget in self.parents_widgets: self.table.remove(widget) @@ -728,7 +742,7 @@ class GitView: button.set_relief(gtk.RELIEF_NONE) button.set_sensitive(True) button.connect("clicked", self._show_clicked_cb, - commit.commit_sha1, parent_id) + commit.commit_sha1, parent_id, self.encoding) hbox.pack_start(button, expand=False, fill=True) button.show() @@ -870,15 +884,15 @@ class GitView: # Reset nodepostion if (last_nodepos > 5): - last_nodepos = -1 + last_nodepos = -1 # Add the incomplete lines of the last cell in this try: colour = self.colours[commit.commit_sha1] except KeyError: self.colours[commit.commit_sha1] = last_colour+1 - last_colour = self.colours[commit.commit_sha1] - colour = self.colours[commit.commit_sha1] + last_colour = self.colours[commit.commit_sha1] + colour = self.colours[commit.commit_sha1] try: node_pos = self.nodepos[commit.commit_sha1] @@ -910,7 +924,7 @@ class GitView: self.colours[parent_id] = last_colour+1 last_colour = self.colours[parent_id] self.nodepos[parent_id] = last_nodepos+1 - last_nodepos = self.nodepos[parent_id] + last_nodepos = self.nodepos[parent_id] in_line.append((node_pos, self.nodepos[parent_id], self.colours[parent_id])) @@ -946,7 +960,7 @@ class GitView: try: next_commit = self.commits[index+1] if (next_commit.commit_sha1 == sha1 and pos != int(pos)): - # join the line back to the node point + # join the line back to the node point # This need to be done only if we modified it in_line.append((pos, pos-0.5, self.colours[sha1])) continue; @@ -967,10 +981,10 @@ class GitView: self.treeview.grab_focus() - def _show_clicked_cb(self, widget, commit_sha1, parent_sha1): + def _show_clicked_cb(self, widget, commit_sha1, parent_sha1, encoding): """Callback for when the show button for a parent is clicked.""" window = DiffWindow() - window.set_diff(commit_sha1, parent_sha1) + window.set_diff(commit_sha1, parent_sha1, encoding) self.treeview.grab_focus() if __name__ == "__main__": diff --git a/count-delta.c b/count-delta.c index 058a2aadb1..3ee3a0ccf1 100644 --- a/count-delta.c +++ b/count-delta.c @@ -3,11 +3,74 @@ * The delta-parsing part is almost straight copy of patch-delta.c * which is (C) 2005 Nicolas Pitre <nico@cam.org>. */ +#include "cache.h" +#include "delta.h" +#include "count-delta.h" #include <stdlib.h> #include <string.h> #include <limits.h> -#include "delta.h" -#include "count-delta.h" + +struct span { + struct span *next; + unsigned long ofs; + unsigned long end; +}; + +static void touch_range(struct span **span, + unsigned long ofs, unsigned long end) +{ + struct span *e = *span; + struct span *p = NULL; + + while (e && e->ofs <= ofs) { + again: + if (ofs < e->end) { + while (e->end < end) { + if (e->next && e->next->ofs <= end) { + e->end = e->next->ofs; + e = e->next; + } + else { + e->end = end; + return; + } + } + return; + } + p = e; + e = e->next; + } + if (e && e->ofs <= end) { + e->ofs = ofs; + goto again; + } + else { + e = xmalloc(sizeof(*e)); + e->ofs = ofs; + e->end = end; + if (p) { + e->next = p->next; + p->next = e; + } + else { + e->next = *span; + *span = e; + } + } +} + +static unsigned long count_range(struct span *s) +{ + struct span *t; + unsigned long sz = 0; + while (s) { + t = s; + sz += s->end - s->ofs; + s = s->next; + free(t); + } + return sz; +} /* * NOTE. We do not _interpret_ delta fully. As an approximation, we @@ -21,10 +84,11 @@ int count_delta(void *delta_buf, unsigned long delta_size, unsigned long *src_copied, unsigned long *literal_added) { - unsigned long copied_from_source, added_literal; + unsigned long added_literal; const unsigned char *data, *top; unsigned char cmd; unsigned long src_size, dst_size, out; + struct span *span = NULL; if (delta_size < DELTA_SIZE_MIN) return -1; @@ -35,7 +99,7 @@ int count_delta(void *delta_buf, unsigned long delta_size, src_size = get_delta_hdr_size(&data); dst_size = get_delta_hdr_size(&data); - added_literal = copied_from_source = out = 0; + added_literal = out = 0; while (data < top) { cmd = *data++; if (cmd & 0x80) { @@ -49,7 +113,7 @@ int count_delta(void *delta_buf, unsigned long delta_size, if (cmd & 0x40) cp_size |= (*data++ << 16); if (cp_size == 0) cp_size = 0x10000; - copied_from_source += cp_size; + touch_range(&span, cp_off, cp_off+cp_size); out += cp_size; } else { /* write literal into dst */ @@ -59,6 +123,8 @@ int count_delta(void *delta_buf, unsigned long delta_size, } } + *src_copied = count_range(span); + /* sanity check */ if (data != top || out != dst_size) return -1; @@ -66,7 +132,6 @@ int count_delta(void *delta_buf, unsigned long delta_size, /* delete size is what was _not_ copied from source. * edit size is that and literal additions. */ - *src_copied = copied_from_source; *literal_added = added_literal; return 0; } @@ -4,7 +4,8 @@ /* handling of delta buffers */ extern void *diff_delta(void *from_buf, unsigned long from_size, void *to_buf, unsigned long to_size, - unsigned long *delta_size, unsigned long max_size); + unsigned long *delta_size, unsigned long max_size, + void **from_index); extern void *patch_delta(void *src_buf, unsigned long src_size, void *delta_buf, unsigned long delta_size, unsigned long *dst_size); diff --git a/diff-delta.c b/diff-delta.c index c2f656ae39..dcd3f5572e 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -19,159 +19,139 @@ */ #include <stdlib.h> +#include <string.h> #include "delta.h" -#include "zlib.h" -/* block size: min = 16, max = 64k, power of 2 */ -#define BLK_SIZE 16 - -#define MIN(a, b) ((a) < (b) ? (a) : (b)) - -#define GR_PRIME 0x9e370001 -#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b))) - -static unsigned int hashbits(unsigned int size) -{ - unsigned int val = 1, bits = 0; - while (val < size && bits < 32) { - val <<= 1; - bits++; - } - return bits ? bits: 1; -} - -typedef struct s_chanode { - struct s_chanode *next; - int icurr; -} chanode_t; - -typedef struct s_chastore { - int isize, nsize; - chanode_t *ancur; -} chastore_t; - -static void cha_init(chastore_t *cha, int isize, int icount) -{ - cha->isize = isize; - cha->nsize = icount * isize; - cha->ancur = NULL; -} - -static void *cha_alloc(chastore_t *cha) -{ - chanode_t *ancur; - void *data; - - ancur = cha->ancur; - if (!ancur || ancur->icurr == cha->nsize) { - ancur = malloc(sizeof(chanode_t) + cha->nsize); - if (!ancur) - return NULL; - ancur->icurr = 0; - ancur->next = cha->ancur; - cha->ancur = ancur; - } - - data = (void *)ancur + sizeof(chanode_t) + ancur->icurr; - ancur->icurr += cha->isize; - return data; -} - -static void cha_free(chastore_t *cha) -{ - chanode_t *cur = cha->ancur; - while (cur) { - chanode_t *tmp = cur; - cur = cur->next; - free(tmp); - } -} - -typedef struct s_bdrecord { - struct s_bdrecord *next; - unsigned int fp; +struct index { const unsigned char *ptr; -} bdrecord_t; + struct index *next; +}; -typedef struct s_bdfile { - chastore_t cha; - unsigned int fphbits; - bdrecord_t **fphash; -} bdfile_t; - -static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf) +static struct index ** delta_index(const unsigned char *buf, + unsigned long bufsize, + unsigned long trg_bufsize) { - unsigned int fphbits; - int i, hsize; - const unsigned char *data, *top; - bdrecord_t *brec; - bdrecord_t **fphash; - - fphbits = hashbits(bufsize / BLK_SIZE + 1); - hsize = 1 << fphbits; - fphash = malloc(hsize * sizeof(bdrecord_t *)); - if (!fphash) - return -1; - for (i = 0; i < hsize; i++) - fphash[i] = NULL; - cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1); - - top = buf + bufsize; - data = buf + (bufsize / BLK_SIZE) * BLK_SIZE; - if (data == top) - data -= BLK_SIZE; - - for ( ; data >= buf; data -= BLK_SIZE) { - brec = cha_alloc(&bdf->cha); - if (!brec) { - cha_free(&bdf->cha); - free(fphash); - return -1; - } - brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data)); - brec->ptr = data; - i = HASH(brec->fp, fphbits); - brec->next = fphash[i]; - fphash[i] = brec; + unsigned long hsize; + unsigned int i, hshift, hlimit, *hash_count; + const unsigned char *data; + struct index *entry, **hash; + void *mem; + + /* determine index hash size */ + hsize = bufsize / 4; + for (i = 8; (1 << i) < hsize && i < 24; i += 2); + hsize = 1 << i; + hshift = (i - 8) / 2; + + /* + * Allocate lookup index. Note the first hash pointer + * is used to store the hash shift value. + */ + mem = malloc((1 + hsize) * sizeof(*hash) + bufsize * sizeof(*entry)); + if (!mem) + return NULL; + hash = mem; + *hash++ = (void *)hshift; + entry = mem + (1 + hsize) * sizeof(*hash); + memset(hash, 0, hsize * sizeof(*hash)); + + /* allocate an array to count hash entries */ + hash_count = calloc(hsize, sizeof(*hash_count)); + if (!hash_count) { + free(hash); + return NULL; } - bdf->fphbits = fphbits; - bdf->fphash = fphash; - - return 0; -} + /* then populate the index */ + data = buf + bufsize - 2; + while (data > buf) { + entry->ptr = --data; + i = data[0] ^ ((data[1] ^ (data[2] << hshift)) << hshift); + entry->next = hash[i]; + hash[i] = entry++; + hash_count[i]++; + } + + /* + * Determine a limit on the number of entries in the same hash + * bucket. This guard us against patological data sets causing + * really bad hash distribution with most entries in the same hash + * bucket that would bring us to O(m*n) computing costs (m and n + * corresponding to reference and target buffer sizes). + * + * The more the target buffer is large, the more it is important to + * have small entry lists for each hash buckets. With such a limit + * the cost is bounded to something more like O(m+n). + */ + hlimit = (1 << 26) / trg_bufsize; + if (hlimit < 16) + hlimit = 16; + + /* + * Now make sure none of the hash buckets has more entries than + * we're willing to test. Otherwise we short-circuit the entry + * list uniformly to still preserve a good repartition across + * the reference buffer. + */ + for (i = 0; i < hsize; i++) { + if (hash_count[i] < hlimit) + continue; + entry = hash[i]; + do { + struct index *keep = entry; + int skip = hash_count[i] / hlimit / 2; + do { + entry = entry->next; + } while(--skip && entry); + keep->next = entry; + } while(entry); + } + free(hash_count); -static void delta_cleanup(bdfile_t *bdf) -{ - free(bdf->fphash); - cha_free(&bdf->cha); + return hash-1; } +/* provide the size of the copy opcode given the block offset and size */ #define COPYOP_SIZE(o, s) \ (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \ !!(s & 0xff) + !!(s & 0xff00) + 1) +/* the maximum size for any opcode */ +#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff) + void *diff_delta(void *from_buf, unsigned long from_size, void *to_buf, unsigned long to_size, unsigned long *delta_size, - unsigned long max_size) + unsigned long max_size, + void **from_index) { - int i, outpos, outsize, inscnt, csize, msize, moff; - unsigned int fp; - const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2; - unsigned char *out, *orig; - bdrecord_t *brec; - bdfile_t bdf; + unsigned int i, outpos, outsize, inscnt, hash_shift; + const unsigned char *ref_data, *ref_top, *data, *top; + unsigned char *out; + struct index *entry, **hash; - if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf)) + if (!from_size || !to_size) return NULL; - + if (from_index && *from_index) { + hash = *from_index; + } else { + hash = delta_index(from_buf, from_size, to_size); + if (!hash) + return NULL; + if (from_index) + *from_index = hash; + } + hash_shift = (unsigned int)(*hash++); + outpos = 0; outsize = 8192; + if (max_size && outsize >= max_size) + outsize = max_size + MAX_OP_SIZE + 1; out = malloc(outsize); if (!out) { - delta_cleanup(&bdf); + if (!from_index) + free(hash-1); return NULL; } @@ -199,28 +179,28 @@ void *diff_delta(void *from_buf, unsigned long from_size, } inscnt = 0; - moff = 0; - while (data < top) { - msize = 0; - fp = adler32(0, data, MIN(top - data, BLK_SIZE)); - i = HASH(fp, bdf.fphbits); - for (brec = bdf.fphash[i]; brec; brec = brec->next) { - if (brec->fp == fp) { - csize = ref_top - brec->ptr; - if (csize > top - data) - csize = top - data; - for (ptr1 = brec->ptr, ptr2 = data; - csize && *ptr1 == *ptr2; - csize--, ptr1++, ptr2++); - csize = ptr1 - brec->ptr; - if (csize > msize) { - moff = brec->ptr - ref_data; - msize = csize; - if (msize >= 0x10000) { - msize = 0x10000; - break; - } + while (data < top) { + unsigned int moff = 0, msize = 0; + if (data + 3 <= top) { + i = data[0] ^ ((data[1] ^ (data[2] << hash_shift)) << hash_shift); + for (entry = hash[i]; entry; entry = entry->next) { + const unsigned char *ref = entry->ptr; + const unsigned char *src = data; + unsigned int ref_size = ref_top - ref; + if (ref_size > top - src) + ref_size = top - src; + if (ref_size > 0x10000) + ref_size = 0x10000; + if (ref_size <= msize) + break; + if (*ref != *src) + continue; + while (ref_size-- && *++src == *++ref); + if (msize < ref - entry->ptr) { + /* this is our best match so far */ + msize = ref - entry->ptr; + moff = entry->ptr - ref_data; } } } @@ -235,13 +215,15 @@ void *diff_delta(void *from_buf, unsigned long from_size, inscnt = 0; } } else { + unsigned char *op; + if (inscnt) { out[outpos - inscnt - 1] = inscnt; inscnt = 0; } data += msize; - orig = out + outpos++; + op = out + outpos++; i = 0x80; if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; } @@ -256,23 +238,22 @@ void *diff_delta(void *from_buf, unsigned long from_size, msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } - *orig = i; - } - - if (max_size && outpos > max_size) { - free(out); - delta_cleanup(&bdf); - return NULL; + *op = i; } - /* next time around the largest possible output is 1 + 4 + 3 */ - if (outpos > outsize - 8) { + if (outpos >= outsize - MAX_OP_SIZE) { void *tmp = out; outsize = outsize * 3 / 2; - out = realloc(out, outsize); + if (max_size && outsize >= max_size) + outsize = max_size + MAX_OP_SIZE + 1; + if (max_size && outpos > max_size) + out = NULL; + else + out = realloc(out, outsize); if (!out) { free(tmp); - delta_cleanup(&bdf); + if (!from_index) + free(hash-1); return NULL; } } @@ -281,7 +262,8 @@ void *diff_delta(void *from_buf, unsigned long from_size, if (inscnt) out[outpos - inscnt - 1] = inscnt; - delta_cleanup(&bdf); + if (!from_index) + free(hash-1); *delta_size = outpos; return out; } @@ -178,11 +178,12 @@ static void emit_rewrite_diff(const char *name_a, copy_file('+', temp[1].name); } -static void builtin_diff(const char *name_a, +static const char *builtin_diff(const char *name_a, const char *name_b, struct diff_tempfile *temp, const char *xfrm_msg, - int complete_rewrite) + int complete_rewrite, + const char **args) { int i, next_at, cmd_size; const char *const diff_cmd = "diff -L%s -L%s"; @@ -242,19 +243,24 @@ static void builtin_diff(const char *name_a, } if (xfrm_msg && xfrm_msg[0]) puts(xfrm_msg); + /* + * we do not run diff between different kind + * of objects. + */ if (strncmp(temp[0].mode, temp[1].mode, 3)) - /* we do not run diff between different kind - * of objects. - */ - exit(0); + return NULL; if (complete_rewrite) { - fflush(NULL); emit_rewrite_diff(name_a, name_b, temp); - exit(0); + return NULL; } } - fflush(NULL); - execlp("/bin/sh","sh", "-c", cmd, NULL); + + /* This is disgusting */ + *args++ = "sh"; + *args++ = "-c"; + *args++ = cmd; + *args = NULL; + return "/bin/sh"; } struct diff_filespec *alloc_filespec(const char *path) @@ -559,6 +565,40 @@ static void remove_tempfile_on_signal(int signo) raise(signo); } +static int spawn_prog(const char *pgm, const char **arg) +{ + pid_t pid; + int status; + + fflush(NULL); + pid = fork(); + if (pid < 0) + die("unable to fork"); + if (!pid) { + execvp(pgm, (char *const*) arg); + exit(255); + } + + while (waitpid(pid, &status, 0) < 0) { + if (errno == EINTR) + continue; + return -1; + } + + /* Earlier we did not check the exit status because + * diff exits non-zero if files are different, and + * we are not interested in knowing that. It was a + * mistake which made it harder to quit a diff-* + * session that uses the git-apply-patch-script as + * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF + * should also exit non-zero only when it wants to + * abort the entire diff-* session. + */ + if (WIFEXITED(status) && !WEXITSTATUS(status)) + return 0; + return -1; +} + /* An external diff command takes: * * diff-cmd name infile1 infile1-sha1 infile1-mode \ @@ -573,9 +613,9 @@ static void run_external_diff(const char *pgm, const char *xfrm_msg, int complete_rewrite) { + const char *spawn_arg[10]; struct diff_tempfile *temp = diff_temp; - pid_t pid; - int status; + int retval; static int atexit_asked = 0; const char *othername; @@ -592,59 +632,41 @@ static void run_external_diff(const char *pgm, signal(SIGINT, remove_tempfile_on_signal); } - fflush(NULL); - pid = fork(); - if (pid < 0) - die("unable to fork"); - if (!pid) { - if (pgm) { - if (one && two) { - const char *exec_arg[10]; - const char **arg = &exec_arg[0]; - *arg++ = pgm; - *arg++ = name; - *arg++ = temp[0].name; - *arg++ = temp[0].hex; - *arg++ = temp[0].mode; - *arg++ = temp[1].name; - *arg++ = temp[1].hex; - *arg++ = temp[1].mode; - if (other) { - *arg++ = other; - *arg++ = xfrm_msg; - } - *arg = NULL; - execvp(pgm, (char *const*) exec_arg); + if (pgm) { + const char **arg = &spawn_arg[0]; + if (one && two) { + *arg++ = pgm; + *arg++ = name; + *arg++ = temp[0].name; + *arg++ = temp[0].hex; + *arg++ = temp[0].mode; + *arg++ = temp[1].name; + *arg++ = temp[1].hex; + *arg++ = temp[1].mode; + if (other) { + *arg++ = other; + *arg++ = xfrm_msg; } - else - execlp(pgm, pgm, name, NULL); + } else { + *arg++ = pgm; + *arg++ = name; } - /* - * otherwise we use the built-in one. - */ - if (one && two) - builtin_diff(name, othername, temp, xfrm_msg, - complete_rewrite); - else + *arg = NULL; + } else { + if (one && two) { + pgm = builtin_diff(name, othername, temp, xfrm_msg, complete_rewrite, spawn_arg); + } else printf("* Unmerged path %s\n", name); - exit(0); } - if (waitpid(pid, &status, 0) < 0 || - !WIFEXITED(status) || WEXITSTATUS(status)) { - /* Earlier we did not check the exit status because - * diff exits non-zero if files are different, and - * we are not interested in knowing that. It was a - * mistake which made it harder to quit a diff-* - * session that uses the git-apply-patch-script as - * the GIT_EXTERNAL_DIFF. A custom GIT_EXTERNAL_DIFF - * should also exit non-zero only when it wants to - * abort the entire diff-* session. - */ - remove_tempfile(); + + retval = 0; + if (pgm) + retval = spawn_prog(pgm, spawn_arg); + remove_tempfile(); + if (retval) { fprintf(stderr, "external diff died, stopping at %s.\n", name); exit(1); } - remove_tempfile(); } static void diff_fill_sha1_info(struct diff_filespec *one) diff --git a/diffcore-break.c b/diffcore-break.c index c57513a4fa..0fc2b860be 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -4,8 +4,6 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" -#include "delta.h" -#include "count-delta.h" static int should_break(struct diff_filespec *src, struct diff_filespec *dst, @@ -47,7 +45,6 @@ static int should_break(struct diff_filespec *src, * The value we return is 1 if we want the pair to be broken, * or 0 if we do not. */ - void *delta; unsigned long delta_size, base_size, src_copied, literal_added; int to_break = 0; @@ -58,6 +55,10 @@ static int should_break(struct diff_filespec *src, if (!S_ISREG(src->mode) || !S_ISREG(dst->mode)) return 0; /* leave symlink rename alone */ + if (src->sha1_valid && dst->sha1_valid && + !memcmp(src->sha1, dst->sha1, 20)) + return 0; /* they are the same */ + if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; /* error but caught downstream */ @@ -65,19 +66,11 @@ static int should_break(struct diff_filespec *src, if (base_size < MINIMUM_BREAK_SIZE) return 0; /* we do not break too small filepair */ - delta = diff_delta(src->data, src->size, - dst->data, dst->size, - &delta_size, 0); - if (!delta) - return 0; /* error but caught downstream */ - - /* Estimate the edit size by interpreting delta. */ - if (count_delta(delta, delta_size, - &src_copied, &literal_added)) { - free(delta); - return 0; /* we cannot tell */ - } - free(delta); + if (diffcore_count_changes(src->data, src->size, + dst->data, dst->size, + 0, + &src_copied, &literal_added)) + return 0; /* Compute merge-score, which is "how much is removed * from the source material". The clean-up stage will diff --git a/diffcore-delta.c b/diffcore-delta.c new file mode 100644 index 0000000000..d03787be65 --- /dev/null +++ b/diffcore-delta.c @@ -0,0 +1,128 @@ +#include "cache.h" +#include "diff.h" +#include "diffcore.h" + +struct linehash { + unsigned long bytes; + unsigned long hash; +}; + +static unsigned long hash_extended_line(const unsigned char **buf_p, + unsigned long left) +{ + /* An extended line is zero or more whitespace letters (including LF) + * followed by one non whitespace letter followed by zero or more + * non LF, and terminated with by a LF (or EOF). + */ + const unsigned char *bol = *buf_p; + const unsigned char *buf = bol; + unsigned long hashval = 0; + while (left) { + unsigned c = *buf++; + if (!c) + goto binary; + left--; + if (' ' < c) { + hashval = c; + break; + } + } + while (left) { + unsigned c = *buf++; + if (!c) + goto binary; + left--; + if (c == '\n') + break; + if (' ' < c) + hashval = hashval * 11 + c; + } + *buf_p = buf; + return hashval; + + binary: + *buf_p = NULL; + return 0; +} + +static int linehash_compare(const void *a_, const void *b_) +{ + struct linehash *a = (struct linehash *) a_; + struct linehash *b = (struct linehash *) b_; + if (a->hash < b->hash) return -1; + if (a->hash > b->hash) return 1; + return 0; +} + +static struct linehash *hash_lines(const unsigned char *buf, + unsigned long size) +{ + const unsigned char *eobuf = buf + size; + struct linehash *line = NULL; + int alloc = 0, used = 0; + + while (buf < eobuf) { + const unsigned char *ptr = buf; + unsigned long hash = hash_extended_line(&buf, eobuf-ptr); + if (!buf) { + free(line); + return NULL; + } + if (alloc <= used) { + alloc = alloc_nr(alloc); + line = xrealloc(line, sizeof(*line) * alloc); + } + line[used].bytes = buf - ptr; + line[used].hash = hash; + used++; + } + qsort(line, used, sizeof(*line), linehash_compare); + + /* Terminate the list */ + if (alloc <= used) + line = xrealloc(line, sizeof(*line) * (used+1)); + line[used].bytes = line[used].hash = 0; + return line; +} + +int diffcore_count_changes(void *src, unsigned long src_size, + void *dst, unsigned long dst_size, + unsigned long delta_limit, + unsigned long *src_copied, + unsigned long *literal_added) +{ + struct linehash *src_lines, *dst_lines; + unsigned long sc, la; + + src_lines = hash_lines(src, src_size); + if (!src_lines) + return -1; + dst_lines = hash_lines(dst, dst_size); + if (!dst_lines) { + free(src_lines); + return -1; + } + sc = la = 0; + while (src_lines->bytes && dst_lines->bytes) { + int cmp = linehash_compare(src_lines, dst_lines); + if (!cmp) { + sc += src_lines->bytes; + src_lines++; + dst_lines++; + continue; + } + if (cmp < 0) { + src_lines++; + continue; + } + la += dst_lines->bytes; + dst_lines++; + } + while (dst_lines->bytes) { + la += dst_lines->bytes; + dst_lines++; + } + *src_copied = sc; + *literal_added = la; + return 0; +} diff --git a/diffcore-rename.c b/diffcore-rename.c index ffd126af0d..55cf1c37f3 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -4,8 +4,6 @@ #include "cache.h" #include "diff.h" #include "diffcore.h" -#include "delta.h" -#include "count-delta.h" /* Table of rename/copy destinations */ @@ -135,7 +133,6 @@ static int estimate_similarity(struct diff_filespec *src, * match than anything else; the destination does not even * call into this function in that case. */ - void *delta; unsigned long delta_size, base_size, src_copied, literal_added; unsigned long delta_limit; int score; @@ -165,28 +162,13 @@ static int estimate_similarity(struct diff_filespec *src, if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; /* error but caught downstream */ - delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE; - delta = diff_delta(src->data, src->size, - dst->data, dst->size, - &delta_size, delta_limit); - if (!delta) - /* If delta_limit is exceeded, we have too much differences */ - return 0; - - /* A delta that has a lot of literal additions would have - * big delta_size no matter what else it does. - */ - if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) { - free(delta); - return 0; - } - /* Estimate the edit size by interpreting delta. */ - if (count_delta(delta, delta_size, &src_copied, &literal_added)) { - free(delta); + delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE; + if (diffcore_count_changes(src->data, src->size, + dst->data, dst->size, + delta_limit, + &src_copied, &literal_added)) return 0; - } - free(delta); /* Extent of damage */ if (src->size + literal_added < src_copied) diff --git a/diffcore.h b/diffcore.h index 12cd816591..7f9889daf3 100644 --- a/diffcore.h +++ b/diffcore.h @@ -18,7 +18,7 @@ #define MAX_SCORE 60000.0 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */ #define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%)*/ -#define DEFAULT_MERGE_SCORE 48000 /* maximum for break-merge to happen (80%)*/ +#define DEFAULT_MERGE_SCORE 45000 /* maximum for break-merge to happen (75%)*/ #define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */ @@ -101,4 +101,10 @@ void diff_debug_queue(const char *, struct diff_queue_struct *); #define diff_debug_queue(a,b) do {} while(0) #endif +extern int diffcore_count_changes(void *src, unsigned long src_size, + void *dst, unsigned long dst_size, + unsigned long delta_limit, + unsigned long *src_copied, + unsigned long *literal_added); + #endif diff --git a/environment.c b/environment.c index 251e53ca09..16c08f0697 100644 --- a/environment.c +++ b/environment.c @@ -17,6 +17,7 @@ int only_use_symrefs = 0; int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = 0; +const char *apply_default_whitespace = NULL; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; @@ -2,7 +2,8 @@ # # Copyright (c) 2005, 2006 Junio C Hamano -USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox> +USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] + [--interactive] [--whitespace=<option>] <mbox>... or, when resuming [--skip | --resolved]' . git-sh-setup @@ -100,7 +101,7 @@ fall_back_3way () { } prec=4 -dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= +dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= while case "$#" in 0) break;; esac do @@ -133,6 +134,9 @@ do --sk|--ski|--skip) skip=t; shift ;; + --whitespace=*) + ws=$1; shift ;; + --) shift; break ;; -*) @@ -171,10 +175,11 @@ else exit 1 } - # -b, -s, -u and -k flags are kept for the resuming session after - # a patch failure. + # -b, -s, -u, -k and --whitespace flags are kept for the + # resuming session after a patch failure. # -3 and -i can and must be given when resuming. echo "$binary" >"$dotest/binary" + echo " $ws" >"$dotest/whitespace" echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" @@ -202,6 +207,7 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi +ws=`cat "$dotest/whitespace"` if test "$(cat "$dotest/sign")" = t then SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e ' @@ -355,7 +361,7 @@ do case "$resolved" in '') - git-apply $binary --index "$dotest/patch" + git-apply $binary --index $ws "$dotest/patch" apply_status=$? ;; t) diff --git a/git-annotate.perl b/git-annotate.perl index f9c2c6caf5..08d479f4b9 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -15,6 +15,8 @@ sub usage() { print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] -l, --long Show long rev (Defaults off) + -t, --time + Show raw timestamp (Defaults off) -r, --rename Follow renames (Defaults on). -S, --rev-file revs-file @@ -26,12 +28,13 @@ sub usage() { exit(1); } -our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1); +our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); my $rc = GetOptions( "long|l" => \$longrev, + "time|t" => \$rawtime, "help|h" => \$help, "rename|r" => \$rename, - "rev-file|S" => \$rev_file); + "rev-file|S=s" => \$rev_file); if (!$rc or $help) { usage(); } @@ -174,7 +177,8 @@ sub git_rev_list { my $revlist; if ($rev_file) { - open($revlist, '<' . $rev_file); + open($revlist, '<' . $rev_file) + or die "Failed to open $rev_file : $!"; } else { $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) or die "Failed to exec git-rev-list: $!"; @@ -304,6 +308,12 @@ sub _git_diff_parse { } $ri++; + } elsif (m/^\\/) { + ; + # Skip \No newline at end of file. + # But this can be internationalized, so only look + # for an initial \ + } else { if (substr($_,1) ne get_line($slines,$ri) ) { die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n", @@ -404,8 +414,10 @@ sub git_commit_info { } sub format_date { + if ($rawtime) { + return $_[0]; + } my ($timestamp, $timezone) = split(' ', $_[0]); - return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp)); } diff --git a/git-commit.sh b/git-commit.sh index f7ee1aadee..d9ec1f14d9 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # Copyright (c) 2006 Junio C Hamano -USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [--author <author>] [[-i | -o] <path>...]' +USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>) [--amend] [-e] [--author <author>] [[-i | -o] <path>...]' SUBDIRECTORY_OK=Yes . git-sh-setup @@ -64,6 +64,22 @@ run_status () { # We always show status for the whole tree. cd "$TOP" + IS_INITIAL="$initial_commit" + REFERENCE=HEAD + case "$amend" in + t) + # If we are amending the initial commit, there + # is no HEAD^1. + if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1 + then + REFERENCE="HEAD^1" + IS_INITIAL= + else + IS_INITIAL=t + fi + ;; + esac + # If TMP_INDEX is defined, that means we are doing # "--only" partial commit, and that index file is used # to build the tree for the commit. Otherwise, if @@ -85,10 +101,10 @@ run_status () { *) echo "# On branch $branch" ;; esac - if test -z "$initial_commit" + if test -z "$IS_INITIAL" then git-diff-index -M --cached --name-status \ - --diff-filter=MDTCRA HEAD | + --diff-filter=MDTCRA $REFERENCE | sed -e ' s/\\/\\\\/g s/ /\\ /g @@ -147,7 +163,7 @@ run_status () { if test -n "$verbose" then - git-diff-index --cached -M -p --diff-filter=MDTCRA HEAD + git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE fi case "$committable" in 0) @@ -173,6 +189,7 @@ also= only= logfile= use_commit= +amend= no_edit= log_given= log_message= @@ -254,6 +271,12 @@ do verify= shift ;; + --a|--am|--ame|--amen|--amend) + amend=t + log_given=t$log_given + use_commit=HEAD + shift + ;; -c) case "$#" in 1) usage ;; esac shift @@ -328,6 +351,15 @@ done ################################################################ # Sanity check options +case "$amend,$initial_commit" in +t,t) + die "You do not have anything to amend." ;; +t,) + if [ -f "$GIT_DIR/MERGE_HEAD" ]; then + die "You are in the middle of a merge -- cannot amend." + fi ;; +esac + case "$log_given" in tt*) die "Only one of -c/-C/-F/-m can be used." ;; @@ -559,13 +591,18 @@ if test -z "$initial_commit" then if [ -f "$GIT_DIR/MERGE_HEAD" ]; then PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"` + elif test -n "$amend"; then + PARENTS=$(git-cat-file commit HEAD | + sed -n -e '/^$/q' -e 's/^parent /-p /p') fi + current=$(git-rev-parse --verify HEAD) else if [ -z "$(git-ls-files)" ]; then echo >&2 Nothing to commit exit 1 fi PARENTS="" + current= fi { diff --git a/git-cvsserver.perl b/git-cvsserver.perl index d20d1a8c4b..7b3ba1b9de 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -53,6 +53,7 @@ my $methods = { 'Entry' => \&req_Entry, 'Modified' => \&req_Modified, 'Unchanged' => \&req_Unchanged, + 'Questionable' => \&req_Questionable, 'Argument' => \&req_Argument, 'Argumentx' => \&req_Argument, 'expand-modules' => \&req_expandmodules, @@ -63,6 +64,7 @@ my $methods = { 'ci' => \&req_ci, 'diff' => \&req_diff, 'log' => \&req_log, + 'rlog' => \&req_log, 'tag' => \&req_CATCHALL, 'status' => \&req_status, 'admin' => \&req_CATCHALL, @@ -459,6 +461,22 @@ sub req_Unchanged #$log->debug("req_Unchanged : $data"); } +# Questionable filename \n +# Response expected: no. Additional data: no. +# Tell the server to check whether filename should be ignored, +# and if not, next time the server sends responses, send (in +# a M response) `?' followed by the directory and filename. +# filename must not contain `/'; it needs to be a file in the +# directory named by the most recent Directory request. +sub req_Questionable +{ + my ( $cmd, $data ) = @_; + + $state->{entries}{$state->{directory}.$data}{questionable} = 1; + + #$log->debug("req_Questionable : $data"); +} + # Argument text \n # Response expected: no. Save argument for use in a subsequent command. # Arguments accumulate until an argument-using command is given, at which @@ -553,8 +571,19 @@ sub req_co my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log); $updater->update(); + $checkout_path =~ s|/$||; # get rid of trailing slashes + + # Eclipse seems to need the Clear-sticky command + # to prepare the 'Entries' file for the new directory. + print "Clear-sticky $checkout_path/\n"; + print $state->{CVSROOT} . "/$checkout_path/\n"; + print "Clear-static-directory $checkout_path/\n"; + print $state->{CVSROOT} . "/$checkout_path/\n"; + # instruct the client that we're checking out to $checkout_path - print "E cvs server: updating $checkout_path\n"; + print "E cvs checkout: Updating $checkout_path\n"; + + my %seendirs = (); foreach my $git ( @{$updater->gethead} ) { @@ -567,21 +596,29 @@ sub req_co print "Mod-time $git->{modified}\n"; # print some information to the client - print "MT +updated\n"; - print "MT text U\n"; if ( defined ( $git->{dir} ) and $git->{dir} ne "./" ) { - print "MT fname $checkout_path/$git->{dir}$git->{name}\n"; + print "M U $checkout_path/$git->{dir}$git->{name}\n"; } else { - print "MT fname $checkout_path/$git->{name}\n"; + print "M U $checkout_path/$git->{name}\n"; } - print "MT newline\n"; - print "MT -updated\n"; + + if (length($git->{dir}) && $git->{dir} ne './' && !exists($seendirs{$git->{dir}})) { + + # Eclipse seems to need the Clear-sticky command + # to prepare the 'Entries' file for the new directory. + print "Clear-sticky $module/$git->{dir}\n"; + print $state->{CVSROOT} . "/$module/$git->{dir}\n"; + print "Clear-static-directory $module/$git->{dir}\n"; + print $state->{CVSROOT} . "/$module/$git->{dir}\n"; + print "E cvs checkout: Updating /$module/$git->{dir}\n"; + $seendirs{$git->{dir}} = 1; + } # instruct client we're sending a file to put in this path - print "Created $checkout_path/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "\n"; + print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n"; - print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; + print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; # this is an "entries" line print "/$git->{name}/1.$git->{revision}///\n"; @@ -612,6 +649,26 @@ sub req_update argsplit("update"); + # + # It may just be a client exploring the available heads/modukles + # in that case, list them as top level directories and leave it + # at that. Eclipse uses this technique to offer you a list of + # projects (heads in this case) to checkout. + # + if ($state->{module} eq '') { + print "E cvs update: Updating .\n"; + opendir HEADS, $state->{CVSROOT} . '/refs/heads'; + while (my $head = readdir(HEADS)) { + if (-f $state->{CVSROOT} . '/refs/heads/' . $head) { + print "E cvs update: New directory `$head'\n"; + } + } + closedir HEADS; + print "ok\n"; + return 1; + } + + # Grab a handle to the SQLite db and do any necessary updates my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); @@ -657,8 +714,27 @@ sub req_update #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev"); - # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified _and_ the user hasn't specified -C - next if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{unchanged} and not exists ( $state->{opt}{C} ) ); + # Files are up to date if the working copy and repo copy have the same revision, + # and the working copy is unmodified _and_ the user hasn't specified -C + next if ( defined ( $wrev ) + and defined($meta->{revision}) + and $wrev == $meta->{revision} + and $state->{entries}{$filename}{unchanged} + and not exists ( $state->{opt}{C} ) ); + + # If the working copy and repo copy have the same revision, + # but the working copy is modified, tell the client it's modified + if ( defined ( $wrev ) + and defined($meta->{revision}) + and $wrev == $meta->{revision} + and not exists ( $state->{opt}{C} ) ) + { + $log->info("Tell the client the file is modified"); + print "MT text U\n"; + print "MT fname $filename\n"; + print "MT newline\n"; + next; + } if ( $meta->{filehash} eq "deleted" ) { @@ -670,7 +746,8 @@ sub req_update print "Removed $dirpart\n"; print "$filepart\n"; } - elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) + elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) + or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) { $log->info("Updating '$filename'"); # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove) @@ -706,6 +783,7 @@ sub req_update # transmit file transmitfile($meta->{filehash}); } else { + $log->info("Updating '$filename'"); my ( $filepart, $dirpart ) = filenamesplit($meta->{name}); my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/"; diff --git a/git-format-patch.sh b/git-format-patch.sh index eb75de4601..2bd26395ec 100755 --- a/git-format-patch.sh +++ b/git-format-patch.sh @@ -174,7 +174,7 @@ titleScript=' process_one () { perl -w -e ' my ($keep_subject, $num, $signoff, $commsg) = @ARGV; -my ($signoff_pattern, $done_header, $done_subject, $signoff_seen, +my ($signoff_pattern, $done_header, $done_subject, $done_separator, $signoff_seen, $last_was_signoff); if ($signoff) { @@ -228,6 +228,11 @@ while (<FH>) { $done_subject = 1; next; } + unless ($done_separator) { + print "\n"; + $done_separator = 1; + next if (/^$/); + } $last_was_signoff = 0; if (/Signed-off-by:/i) { diff --git a/git-mv.perl b/git-mv.perl index 2ea852c918..fe9c40e1b9 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -19,25 +19,26 @@ EOT exit(1); } -my $GIT_DIR = `git rev-parse --git-dir`; -exit 1 if $?; # rev-parse would have given "not a git dir" message. -chomp($GIT_DIR); - our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v); getopts("hnfkv") || usage; usage() if $opt_h; @ARGV >= 1 or usage; +my $GIT_DIR = `git rev-parse --git-dir`; +exit 1 if $?; # rev-parse would have given "not a git dir" message. +chomp($GIT_DIR); + my (@srcArgs, @dstArgs, @srcs, @dsts); my ($src, $dst, $base, $dstDir); +# remove any trailing slash in arguments +for (@ARGV) { s/\/*$//; } + my $argCount = scalar @ARGV; if (-d $ARGV[$argCount-1]) { $dstDir = $ARGV[$argCount-1]; - # remove any trailing slash - $dstDir =~ s/\/$//; @srcArgs = @ARGV[0..$argCount-2]; - + foreach $src (@srcArgs) { $base = $src; $base =~ s/^.*\///; @@ -46,10 +47,14 @@ if (-d $ARGV[$argCount-1]) { } } else { - if ($argCount != 2) { + if ($argCount < 2) { + print "Error: need at least two arguments\n"; + exit(1); + } + if ($argCount > 2) { print "Error: moving to directory '" . $ARGV[$argCount-1] - . "' not possible; not exisiting\n"; + . "' not possible; not existing\n"; exit(1); } @srcArgs = ($ARGV[0]); @@ -57,6 +62,16 @@ else { $dstDir = ""; } +# normalize paths, needed to compare against versioned files and update-index +# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c" +for (@srcArgs, @dstArgs) { + s|^\./||; + s|/\./|/| while (m|/\./|); + s|//+|/|g; + # Also "a/b/../c" ==> "a/c" + 1 while (s,(^|/)[^/]+/\.\./,$1,); +} + my (@allfiles,@srcfiles,@dstfiles); my $safesrc; my (%overwritten, %srcForDst); diff --git a/git-svnimport.perl b/git-svnimport.perl index ee2940f480..639aa41861 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -13,6 +13,7 @@ use strict; use warnings; use Getopt::Std; +use File::Copy; use File::Spec; use File::Temp qw(tempfile); use File::Path qw(mkpath); @@ -29,19 +30,21 @@ die "Need SVN:Core 1.2.1 or better" if $SVN::Core::VERSION lt "1.2.1"; $SIG{'PIPE'}="IGNORE"; $ENV{'TZ'}="UTC"; -our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,$opt_b,$opt_r,$opt_s,$opt_l,$opt_d,$opt_D); +our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T, + $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D); sub usage() { print STDERR <<END; Usage: ${\basename $0} # fetch/update GIT from SVN [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname] - [-d|-D] [-i] [-u] [-r] [-s start_chg] [-m] [-M regex] [SVN_URL] + [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg] + [-m] [-M regex] [-A author_file] [SVN_URL] END exit(1); } -getopts("b:C:dDhil:mM:o:rs:t:T:uv") or usage(); +getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:uv") or usage(); usage if $opt_h; my $tag_name = $opt_t || "tags"; @@ -66,6 +69,25 @@ if ($opt_M) { push (@mergerx, qr/$opt_M/); } +# Absolutize filename now, since we will have chdir'ed by the time we +# get around to opening it. +$opt_A = File::Spec->rel2abs($opt_A) if $opt_A; + +our %users = (); +our $users_file = undef; +sub read_users($) { + $users_file = File::Spec->rel2abs(@_); + die "Cannot open $users_file\n" unless -f $users_file; + open(my $authors,$users_file); + while(<$authors>) { + chomp; + next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + (my $user,my $name,my $email) = ($1,$2,$3); + $users{$user} = [$name,$email]; + } + close($authors); +} + select(STDERR); $|=1; select(STDOUT); @@ -112,16 +134,40 @@ sub file { DIR => File::Spec->tmpdir(), UNLINK => 1); print "... $rev $path ...\n" if $opt_v; - my $pool = SVN::Pool->new(); - eval { $self->{'svn'}->get_file($path,$rev,$fh,$pool); }; - $pool->clear; + my (undef, $properties); + eval { (undef, $properties) + = $self->{'svn'}->get_file($path,$rev,$fh); }; if($@) { return undef if $@ =~ /Attempted to get checksum/; die $@; } + my $mode; + if (exists $properties->{'svn:executable'}) { + $mode = '0755'; + } else { + $mode = '0644'; + } close ($fh); - return $name; + return ($name, $mode); +} + +sub ignore { + my($self,$path,$rev) = @_; + + print "... $rev $path ...\n" if $opt_v; + my (undef,undef,$properties) + = $self->{'svn'}->get_dir($path,$rev,undef); + if (exists $properties->{'svn:ignore'}) { + my ($fh, $name) = tempfile('gitsvn.XXXXXX', + DIR => File::Spec->tmpdir(), + UNLINK => 1); + print $fh $properties->{'svn:ignore'}; + close($fh); + return $name; + } else { + return undef; + } } package main; @@ -263,6 +309,14 @@ EOM -d $git_dir or die "Could not create git subdir ($git_dir).\n"; +my $default_authors = "$git_dir/svn-authors"; +if ($opt_A) { + read_users($opt_A); + copy($opt_A,$default_authors) or die "Copy failed: $!"; +} else { + read_users($default_authors) if -f $default_authors; +} + open BRANCHES,">>", "$git_dir/svn2git"; sub node_kind($$$) { @@ -296,7 +350,7 @@ sub get_file($$$) { my $svnpath = revert_split_path($branch,$path); # now get it - my $name; + my ($name,$mode); if($opt_d) { my($req,$res); @@ -316,8 +370,9 @@ sub get_file($$$) { return undef if $res->code == 301; # directory? die $res->status_line." at $url\n"; } + $mode = '0644'; # can't obtain mode via direct http request? } else { - $name = $svn->file("$svnpath",$rev); + ($name,$mode) = $svn->file("$svnpath",$rev); return undef unless defined $name; } @@ -331,10 +386,37 @@ sub get_file($$$) { chomp $sha; close $F; unlink $name; - my $mode = "0644"; # SV does not seem to store any file modes return [$mode, $sha, $path]; } +sub get_ignore($$$$$) { + my($new,$old,$rev,$branch,$path) = @_; + + return unless $opt_I; + my $svnpath = revert_split_path($branch,$path); + my $name = $svn->ignore("$svnpath",$rev); + if ($path eq '/') { + $path = $opt_I; + } else { + $path = File::Spec->catfile($path,$opt_I); + } + if (defined $name) { + my $pid = open(my $F, '-|'); + die $! unless defined $pid; + if (!$pid) { + exec("git-hash-object", "-w", $name) + or die "Cannot create object: $!\n"; + } + my $sha = <$F>; + chomp $sha; + close $F; + unlink $name; + push(@$new,['0644',$sha,$path]); + } else { + push(@$old,$path); + } +} + sub split_path($$) { my($rev,$path) = @_; my $branch; @@ -431,6 +513,10 @@ sub commit { if (not defined $author) { $author_name = $author_email = "unknown"; + } elsif (defined $users_file) { + die "User $author is not listed in $users_file\n" + unless exists $users{$author}; + ($author_name,$author_email) = @{$users{$author}}; } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { ($author_name, $author_email) = ($1, $2); } else { @@ -540,6 +626,9 @@ sub commit { my $opath = $action->[3]; print STDERR "$revision: $branch: could not fetch '$opath'\n"; } + } elsif ($node_kind eq $SVN::Node::dir) { + get_ignore(\@new, \@old, $revision, + $branch,$path); } } elsif ($action->[0] eq "D") { push(@old,$path); @@ -548,6 +637,9 @@ sub commit { if ($node_kind eq $SVN::Node::file) { my $f = get_file($revision,$branch,$path); push(@new,$f) if $f; + } elsif ($node_kind eq $SVN::Node::dir) { + get_ignore(\@new, \@old, $revision, + $branch,$path); } } else { die "$revision: unknown action '".$action->[0]."' for $path\n"; diff --git a/git-verify-tag.sh b/git-verify-tag.sh index 726b1e706b..36f171b302 100755 --- a/git-verify-tag.sh +++ b/git-verify-tag.sh @@ -4,9 +4,21 @@ USAGE='<tag>' SUBDIRECTORY_OK='Yes' . git-sh-setup +verbose= +while case $# in 0) break;; esac +do + case "$1" in + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + *) + break ;; + esac + shift +done + if [ "$#" != "1" ] then - usage + usage fi type="$(git-cat-file -t "$1" 2>/dev/null)" || @@ -15,6 +27,13 @@ type="$(git-cat-file -t "$1" 2>/dev/null)" || test "$type" = tag || die "$1: cannot verify a non-tag object of type $type." +case "$verbose" in +t) + git-cat-file -p "$1" | + sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p + ;; +esac + git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1 cat "$GIT_DIR/.tmp-vtag" | sed '/-----BEGIN PGP/Q' | @@ -256,12 +256,67 @@ static int cmd_log(int argc, char **argv, char **envp) struct rev_info rev; struct commit *commit; char *buf = xmalloc(LOGSIZE); + static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT; + int abbrev = DEFAULT_ABBREV; + int show_parents = 0; + const char *commit_prefix = "commit "; argc = setup_revisions(argc, argv, &rev, "HEAD"); + while (1 < argc) { + char *arg = argv[1]; + if (!strncmp(arg, "--pretty", 8)) { + commit_format = get_commit_format(arg + 8); + if (commit_format == CMIT_FMT_ONELINE) + commit_prefix = ""; + } + else if (!strcmp(arg, "--parents")) { + show_parents = 1; + } + else if (!strcmp(arg, "--no-abbrev")) { + abbrev = 0; + } + else if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = strtoul(arg + 9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (40 < abbrev) + abbrev = 40; + } + else + die("unrecognized argument: %s", arg); + argc--; argv++; + } + prepare_revision_walk(&rev); setup_pager(); while ((commit = get_revision(&rev)) != NULL) { - pretty_print_commit(CMIT_FMT_DEFAULT, commit, ~0, buf, LOGSIZE, 18); + printf("%s%s", commit_prefix, + sha1_to_hex(commit->object.sha1)); + if (show_parents) { + struct commit_list *parents = commit->parents; + while (parents) { + struct object *o = &(parents->item->object); + parents = parents->next; + if (o->flags & TMP_MARK) + continue; + printf(" %s", sha1_to_hex(o->sha1)); + o->flags |= TMP_MARK; + } + /* TMP_MARK is a general purpose flag that can + * be used locally, but the user should clean + * things up after it is done with them. + */ + for (parents = commit->parents; + parents; + parents = parents->next) + parents->item->object.flags &= ~TMP_MARK; + } + if (commit_format == CMIT_FMT_ONELINE) + putchar(' '); + else + putchar('\n'); + pretty_print_commit(commit_format, commit, ~0, buf, + LOGSIZE, abbrev); printf("%s\n", buf); } free(buf); diff --git a/pack-objects.c b/pack-objects.c index 21ee572f48..d6a3463604 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -99,7 +99,7 @@ static int reused_delta = 0; static int pack_revindex_ix(struct packed_git *p) { - unsigned long ui = (unsigned long)(long)p; + unsigned long ui = (unsigned long)p; int i; ui = ui ^ (ui >> 16); /* defeat structure alignment */ @@ -204,7 +204,7 @@ static void *delta_against(void *buf, unsigned long size, struct object_entry *e if (!otherbuf) die("unable to read %s", sha1_to_hex(entry->delta->sha1)); delta_buf = diff_delta(otherbuf, othersize, - buf, size, &delta_size, 0); + buf, size, &delta_size, 0, NULL); if (!delta_buf || delta_size != entry->delta_size) die("delta size changed"); free(buf); @@ -810,6 +810,7 @@ static int type_size_sort(const struct object_entry *a, const struct object_entr struct unpacked { struct object_entry *entry; void *data; + void **delta_index; }; /* @@ -891,7 +892,8 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de if (sizediff >= max_size) return -1; delta_buf = diff_delta(old->data, oldsize, - cur->data, size, &delta_size, max_size); + cur->data, size, &delta_size, + max_size, old->delta_index); if (!delta_buf) return 0; cur_entry->delta = old_entry; @@ -948,6 +950,7 @@ static void find_deltas(struct object_entry **list, int window, int depth) */ continue; + free(n->delta_index); free(n->data); n->entry = entry; n->data = read_sha1_file(entry->sha1, type, &size); @@ -974,8 +977,10 @@ static void find_deltas(struct object_entry **list, int window, int depth) if (progress) fputc('\n', stderr); - for (i = 0; i < window; ++i) + for (i = 0; i < window; ++i) { + free(array[i].delta_index); free(array[i].data); + } free(array); } diff --git a/read-tree.c b/read-tree.c index f39fe5ca65..be29b3fe11 100644 --- a/read-tree.c +++ b/read-tree.c @@ -560,9 +560,11 @@ static int threeway_merge(struct cache_entry **stages) */ if ((head_deleted && remote_deleted) || (head_deleted && remote && remote_match) || - (remote_deleted && head && head_match)) + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index); return 0; - + } /* * Added in both, identically. */ @@ -704,7 +706,7 @@ static int read_cache_unmerged(void) return deleted; } -static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u | -i] <sha1> [<sha2> [<sha3>]])"; +static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])"; static struct cache_file cache_file; @@ -151,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u break; continue; } - if (read_ref(git_path("%s", path), sha1) < 0) + if (read_ref(git_path("%s", path), sha1) < 0) { + fprintf(stderr, "%s points nowhere!", path); continue; - if (!has_sha1_file(sha1)) + } + if (!has_sha1_file(sha1)) { + fprintf(stderr, "%s does not point to a valid " + "commit object!", path); continue; + } retval = fn(path, sha1); if (retval) break; diff --git a/rev-list.c b/rev-list.c index 6af8d869ee..8e4d83efba 100644 --- a/rev-list.c +++ b/rev-list.c @@ -7,10 +7,9 @@ #include "diff.h" #include "revision.h" -/* bits #0-3 in revision.h */ +/* bits #0-4 in revision.h */ -#define COUNTED (1u << 4) -#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */ +#define COUNTED (1u<<5) static const char rev_list_usage[] = "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n" diff --git a/revision.c b/revision.c index c84f14609b..a3df810076 100644 --- a/revision.c +++ b/revision.c @@ -482,6 +482,21 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->max_count = atoi(arg + 12); continue; } + /* accept -<digit>, like traditilnal "head" */ + if ((*arg == '-') && isdigit(arg[1])) { + revs->max_count = atoi(arg + 1); + continue; + } + if (!strcmp(arg, "-n")) { + if (argc <= i + 1) + die("-n requires an argument"); + revs->max_count = atoi(argv[++i]); + continue; + } + if (!strncmp(arg,"-n",2)) { + revs->max_count = atoi(arg + 2); + continue; + } if (!strncmp(arg, "--max-age=", 10)) { revs->max_age = atoi(arg + 10); revs->limited = 1; @@ -492,6 +507,26 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch revs->limited = 1; continue; } + if (!strncmp(arg, "--since=", 8)) { + revs->max_age = approxidate(arg + 8); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--after=", 8)) { + revs->max_age = approxidate(arg + 8); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--before=", 9)) { + revs->min_age = approxidate(arg + 9); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--until=", 8)) { + revs->min_age = approxidate(arg + 8); + revs->limited = 1; + continue; + } if (!strcmp(arg, "--all")) { handle_all(revs, flags); continue; diff --git a/revision.h b/revision.h index 0043c1694c..31e8f61567 100644 --- a/revision.h +++ b/revision.h @@ -5,6 +5,7 @@ #define UNINTERESTING (1u<<1) #define TREECHANGE (1u<<2) #define SHOWN (1u<<3) +#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */ struct rev_info { /* Starting list */ diff --git a/show-branch.c b/show-branch.c index 5a86ae2f9e..452e63f786 100644 --- a/show-branch.c +++ b/show-branch.c @@ -5,7 +5,7 @@ #include "refs.h" static const char show_branch_usage[] = -"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [<refs>...]"; +"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; static int default_num = 0; static int default_alloc = 0; @@ -547,6 +547,7 @@ int main(int ac, char **av) int shown_merge_point = 0; int with_current_branch = 0; int head_at = -1; + int topics = 0; setup_git_directory(); git_config(git_show_branch_config); @@ -587,6 +588,8 @@ int main(int ac, char **av) independent = 1; else if (!strcmp(arg, "--topo-order")) lifo = 1; + else if (!strcmp(arg, "--topics")) + topics = 1; else if (!strcmp(arg, "--date-order")) lifo = 0; else @@ -729,6 +732,20 @@ int main(int ac, char **av) if (1 < num_rev) { int is_merge = !!(commit->parents && commit->parents->next); + if (topics) { + int interesting = 0; + for (i = 1; i < num_rev; i++) { + if ((this_flag & + (1u << (i + REV_SHIFT)))) { + interesting = 1; + break; + } + } + if (!interesting) + continue; + } + + for (i = 0; i < num_rev; i++) { int mark; if (!(this_flag & (1u << (i + REV_SHIFT)))) diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh new file mode 100755 index 0000000000..cae179436c --- /dev/null +++ b/t/t8001-annotate.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +test_description='git-annotate' +. ./test-lib.sh + +test_expect_success \ + 'prepare reference tree' \ + 'echo "1A quick brown fox jumps over the" >file && + echo "lazy dog" >>file && + git add file + GIT_AUTHOR_NAME="A" git commit -a -m "Initial."' + +test_expect_success \ + 'check all lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]' + +test_expect_success \ + 'Setup new lines blamed on B' \ + 'echo "2A quick brown fox jumps over the" >>file && + echo "lazy dog" >> file && + GIT_AUTHOR_NAME="B" git commit -a -m "Second."' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]' + +test_expect_success \ + 'Two lines blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]' + +test_expect_success \ + 'merge-setup part 1' \ + 'git checkout -b branch1 master && + echo "3A slow green fox jumps into the" >> file && + echo "well." >> file && + GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]' + +test_expect_success \ + 'Two lines blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]' + +test_expect_success \ + 'Two lines blamed on B1' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]' + +test_expect_success \ + 'merge-setup part 2' \ + 'git checkout -b branch2 master && + sed -i -e "s/2A quick brown/4A quick brown lazy dog/" file && + GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]' + +test_expect_success \ + 'One line blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]' + +test_expect_success \ + 'One line blamed on B2' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]' + + +test_expect_success \ + 'merge-setup part 3' \ + 'git pull . branch1' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]' + +test_expect_success \ + 'One line blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]' + +test_expect_success \ + 'Two lines blamed on B1' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]' + +test_expect_success \ + 'One line blamed on B2' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]' + +test_done diff --git a/test-delta.c b/test-delta.c index 1be8ee0c72..89eb68ed21 100644 --- a/test-delta.c +++ b/test-delta.c @@ -63,7 +63,7 @@ int main(int argc, char *argv[]) if (argv[1][1] == 'd') out_buf = diff_delta(from_buf, from_size, data_buf, data_size, - &out_size, 0); + &out_size, 0, NULL); else out_buf = patch_delta(from_buf, from_size, data_buf, data_size, |