diff options
58 files changed, 1741 insertions, 440 deletions
diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt index c1ebd6997a..7386e66dc9 100644 --- a/Documentation/RelNotes-1.5.4.txt +++ b/Documentation/RelNotes-1.5.4.txt @@ -132,6 +132,10 @@ Updates since v1.5.3 variable used to mean "do not require -f option to lose untracked files", but we now use the safer default). + * The kinds of whitespace errors "git diff" and "git apply" notice (and + fix) can be controlled via 'core.whitespace' configuration variable + and 'whitespace' attribute in .gitattributes file. + * "git push" learned --dry-run option to show what would happen if a push is run. @@ -200,6 +204,12 @@ Updates since v1.5.3 * "git bisect" learned "skip" action to mark untestable commits. + * "git bisect visualize" learned a shorter synonym "git bisect view". + + * "git bisect visualize" runs "git log" in a non-windowed + environments. It also can be told what command to run (e.g. "git + bisect visualize tig"). + * "git format-patch" learned "format.numbered" configuration variable to automatically turn --numbered option on when more than one commits are formatted. @@ -220,6 +230,9 @@ Updates since v1.5.3 * "git checkout" from a subdirectory learned to use "../path" to allow checking out a path outside the current directory without cd'ing up. + * "git checkout" from and to detached HEAD leaves a bit more + information in the reflog. + * "git send-email --dry-run" shows full headers for easier diagnosis. * "git merge-ours" is now built-in. @@ -233,11 +246,11 @@ Updates since v1.5.3 descriptive name from From: and Signed-off-by: lines in the commit message. - * "git status" from a subdirectory now shows relative paths which makes - copy-and-pasting for git-checkout/git-add/git-rm easier. - - * "git checkout" from and to detached HEAD leaves a bit more - information in the reflog. + * "git status" from a subdirectory now shows relative paths, which + makes copy-and-pasting for git-checkout/git-add/git-rm easier. The + traditional behaviour to show the full path relative to the top of + the work tree can be had by setting status.relativepaths + configuration variable to true. * In addition there are quite a few internal clean-ups. Notably @@ -266,6 +279,6 @@ series. -- exec >/var/tmp/1 -O=v1.5.3.7-1111-gd9f4059 +O=v1.5.3.7-1148-gcf7e147 echo O=`git describe refs/heads/master` git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 79d51f26cc..fabe7f859f 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -295,6 +295,20 @@ core.pager:: The command that git will use to paginate output. Can be overridden with the `GIT_PAGER` environment variable. +core.whitespace:: + A comma separated list of common whitespace problems to + notice. `git diff` will use `color.diff.whitespace` to + highlight them, and `git apply --whitespace=error` will + consider them as errors: ++ +* `trailing-space` treats trailing whitespaces at the end of the line + as an error (enabled by default). +* `space-before-tab` treats a space character that appears immediately + before a tab character in the initial indent part of the line as an + error (enabled by default). +* `indent-with-non-tab` treats a line that is indented with 8 or more + space characters that can be replaced with tab characters. + alias.*:: Command aliases for the gitlink:git[1] command wrapper - e.g. after defining "alias.last = cat-file commit HEAD", the invocation @@ -387,8 +401,8 @@ color.diff.<slot>:: which part of the patch to use the specified color, and is one of `plain` (context text), `meta` (metainformation), `frag` (hunk header), `old` (removed lines), `new` (added lines), - `commit` (commit headers), or `whitespace` (highlighting dubious - whitespace). The values of these variables may be specified as + `commit` (commit headers), or `whitespace` (highlighting + whitespace errors). The values of these variables may be specified as in color.branch.<slot>. color.interactive:: diff --git a/Documentation/diff-format.txt b/Documentation/diff-format.txt index 2c3a4c433b..400cbb3b1c 100644 --- a/Documentation/diff-format.txt +++ b/Documentation/diff-format.txt @@ -84,3 +84,64 @@ all parents. include::diff-generate-patch.txt[] + + +other diff formats +------------------ + +The `--summary` option describes newly added, deleted, renamed and +copied files. The `--stat` option adds diffstat(1) graph to the +output. These options can be combined with other options, such as +`-p`, and are meant for human consumption. + +When showing a change that involves a rename or a copy, `--stat` output +formats the pathnames compactly by combining common prefix and suffix of +the pathnames. For example, a change that moves `arch/i386/Makefile` to +`arch/x86/Makefile` while modifying 4 lines will be shown like this: + +------------------------------------ +arch/{i386 => x86}/Makefile | 4 +-- +------------------------------------ + +The `--numstat` option gives the diffstat(1) information but is designed +for easier machine consumption. An entry in `--numstat` output looks +like this: + +---------------------------------------- +1 2 README +3 1 arch/{i386 => x86}/Makefile +---------------------------------------- + +That is, from left to right: + +. the number of added lines; +. a tab; +. the number of deleted lines; +. a tab; +. pathname (possibly with rename/copy information); +. a newline. + +When `-z` output option is in effect, the output is formatted this way: + +---------------------------------------- +1 2 README NUL +3 1 NUL arch/i386/Makefile NUL arch/x86/Makefile NUL +---------------------------------------- + +That is: + +. the number of added lines; +. a tab; +. the number of deleted lines; +. a tab; +. a NUL (only exists if renamed/copied); +. pathname in preimage; +. a NUL (only exists if renamed/copied); +. pathname in postimage (only exists if renamed/copied); +. a NUL. + +The extra `NUL` before the preimage path in renamed case is to allow +scripts that read the output to tell if the current record being read is +a single-path record or a rename/copy record without reading ahead. +After reading added and deleted lines, reading up to `NUL` would yield +the pathname, but if that is `NUL`, the record will show two paths. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index d0154bbc0a..5d22b7b58c 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -175,19 +175,19 @@ endif::git-format-patch[] Shorthand for "--text". --ignore-space-at-eol:: - Ignore changes in white spaces at EOL. + Ignore changes in whitespace at EOL. --ignore-space-change:: - Ignore changes in amount of white space. This ignores white - space at line end, and consider all other sequences of one or - more white space characters to be equivalent. + Ignore changes in amount of whitespace. This ignores whitespace + at line end, and considers all other sequences of one or + more whitespace characters to be equivalent. -b:: Shorthand for "--ignore-space-change". --ignore-all-space:: - Ignore white space when comparing lines. This ignores - difference even if one line has white space where the other + Ignore whitespace when comparing lines. This ignores + differences even if one line has whitespace where the other line has none. -w:: diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index bf94cd43bd..721ca998c1 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -65,7 +65,7 @@ OPTIONS operation to a subset of the working tree. See ``Interactive mode'' for details. --p, \--patch: +-p, \--patch:: Similar to Interactive mode but the initial command loop is bypassed and the 'patch' subcommand is invoked using each of the specified filepatterns before exiting. diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index c1c54bfe0b..9ec38f92ba 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -13,7 +13,7 @@ SYNOPSIS [--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] [--cached] - [--whitespace=<nowarn|warn|error|error-all|strip>] + [--whitespace=<nowarn|warn|fix|error|error-all>] [--exclude=PATH] [--verbose] [<patch>...] DESCRIPTION @@ -119,7 +119,7 @@ discouraged. --no-add:: When applying a patch, ignore additions made by the - patch. This can be used to extract common part between + patch. This can be used to extract the common part between two files by first running `diff` on them and applying the result with this option, which would apply the deletion part but not addition part. @@ -135,25 +135,32 @@ discouraged. be useful when importing patchsets, where you want to exclude certain files or directories. ---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 gitlink:git-apply[1] is used for statistics and not applying a - patch, it defaults to `nowarn`. - You can use different `<option>` to control this - behavior: +--whitespace=<action>:: + When applying a patch, detect a new or modified line that has + whitespace errors. What are considered whitespace errors is + controlled by `core.whitespace` configuration. By default, + trailing whitespaces (including lines that solely consist of + whitespaces) and a space character that is immediately followed + by a tab character inside the initial indent of the line are + considered whitespace errors. ++ +By default, the command outputs warning messages but applies the patch. +When gitlink:git-apply[1] is used for statistics and not applying a +patch, it defaults to `nowarn`. ++ +You can use different `<action>` to control this +behavior: + * `nowarn` turns off the trailing whitespace warning. * `warn` outputs warnings for a few such errors, but applies the - patch (default). + patch as-is (default). +* `fix` outputs warnings for a few such errors, and applies the + patch after fixing them (`strip` is a synonym --- the tool + used to consider only trailing whitespaces as errors, and the + fix involved 'stripping' them, but modern gits do more). * `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. --inaccurate-eof:: Under certain circumstances, some versions of diff do not correctly diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 4bb2791550..4261384158 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -86,7 +86,7 @@ OPTIONS Add Signed-off-by line at the end of the commit message. --no-verify:: - This option bypasses the pre-commit hook. + This option bypasses the pre-commit and commit-msg hooks. See also link:hooks.html[hooks]. --allow-empty:: diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index a1bb9bd829..5c5a480ec4 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -28,13 +28,6 @@ If there is no path that is different between the index file and the current HEAD commit (i.e., there is nothing to commit by running `git-commit`), the command exits with non-zero status. -If any paths have been touched in the working tree (that is, -their modification times have changed) but their contents and -permissions are identical to those in the index file, the command -updates the index file. Running `git-status` can thus speed up -subsequent operations such as `git-diff` if the working tree -contains many paths that have been touched but not modified. - OUTPUT ------ diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 19bd25f299..71c7ad76d5 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -361,6 +361,37 @@ When left unspecified, the driver itself is used for both internal merge and the final merge. +Checking whitespace errors +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`whitespace` +^^^^^^^^^^^^ + +The `core.whitespace` configuration variable allows you to define what +`diff` and `apply` should consider whitespace errors for all paths in +the project (See gitlink:git-config[1]). This attribute gives you finer +control per path. + +Set:: + + Notice all types of potential whitespace errors known to git. + +Unset:: + + Do not notice anything as error. + +Unspecified:: + + Use the value of `core.whitespace` configuration variable to + decide what to notice as error. + +String:: + + Specify a comma separate list of common whitespace problems to + notice in the same format as `core.whitespace` configuration + variable. + + EXAMPLE ------- diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index fc1874424e..65f55e4ced 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -244,8 +244,7 @@ This commit is referred to as a "merge commit", or sometimes just a The unique identifier of an <<def_object,object>>. The <<def_hash,hash>> of the object's contents using the Secure Hash Algorithm 1 and usually represented by the 40 character hexadecimal encoding of - the <<def_hash,hash>> of the object (possibly followed by - a white space). + the <<def_hash,hash>> of the object. [[def_object_type]]object type:: One of the identifiers @@ -313,7 +313,7 @@ LIB_OBJS = \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \ - transport.o bundle.o walker.o parse-options.o + transport.o bundle.o walker.o parse-options.o ws.o BUILTIN_OBJS = \ builtin-add.o \ @@ -1025,7 +1025,7 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL) git$X '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install - $(MAKE) -C perl prefix='$(prefix_SQ)' install + $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui install diff --git a/builtin-apply.c b/builtin-apply.c index 91f8752ff7..f2e9a332ca 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -45,14 +45,14 @@ static const char *fake_ancestor; static int line_termination = '\n'; static unsigned long p_context = ULONG_MAX; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; - -static enum whitespace_eol { - nowarn_whitespace, - warn_on_whitespace, - error_on_whitespace, - strip_whitespace, -} new_whitespace = warn_on_whitespace; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>..."; + +static enum ws_error_action { + nowarn_ws_error, + warn_on_ws_error, + die_on_ws_error, + correct_ws_error, +} ws_error_action = warn_on_ws_error; static int whitespace_error; static int squelch_whitespace_errors = 5; static int applied_after_fixing_ws; @@ -61,28 +61,28 @@ static const char *patch_input_file; static void parse_whitespace_option(const char *option) { if (!option) { - new_whitespace = warn_on_whitespace; + ws_error_action = warn_on_ws_error; return; } if (!strcmp(option, "warn")) { - new_whitespace = warn_on_whitespace; + ws_error_action = warn_on_ws_error; return; } if (!strcmp(option, "nowarn")) { - new_whitespace = nowarn_whitespace; + ws_error_action = nowarn_ws_error; return; } if (!strcmp(option, "error")) { - new_whitespace = error_on_whitespace; + ws_error_action = die_on_ws_error; return; } if (!strcmp(option, "error-all")) { - new_whitespace = error_on_whitespace; + ws_error_action = die_on_ws_error; squelch_whitespace_errors = 0; return; } - if (!strcmp(option, "strip")) { - new_whitespace = strip_whitespace; + if (!strcmp(option, "strip") || !strcmp(option, "fix")) { + ws_error_action = correct_ws_error; return; } die("unrecognized whitespace option '%s'", option); @@ -90,11 +90,8 @@ static void parse_whitespace_option(const char *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); - } + if (!whitespace_option && !apply_default_whitespace) + ws_error_action = (apply ? warn_on_ws_error : nowarn_ws_error); } /* @@ -137,11 +134,17 @@ struct fragment { #define BINARY_DELTA_DEFLATED 1 #define BINARY_LITERAL_DEFLATED 2 +/* + * This represents a "patch" to a file, both metainfo changes + * such as creation/deletion, filemode and content changes represented + * as a series of fragments. + */ struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; int is_new, is_delete; /* -1 = unknown, 0 = false, 1 = true */ int rejected; + unsigned ws_rule; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -158,7 +161,8 @@ struct patch { struct patch *next; }; -static void say_patch_name(FILE *output, const char *pre, struct patch *patch, const char *post) +static void say_patch_name(FILE *output, const char *pre, + struct patch *patch, const char *post) { fputs(pre, output); if (patch->old_name && patch->new_name && @@ -229,7 +233,8 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) if (*line == '"') { struct strbuf name; - /* Proposed "new-style" GNU patch/diff format; see + /* + * Proposed "new-style" GNU patch/diff format; see * http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2 */ strbuf_init(&name, 0); @@ -499,7 +504,8 @@ static int gitdiff_dissimilarity(const char *line, struct patch *patch) static int gitdiff_index(const char *line, struct patch *patch) { - /* index line is N hexadecimal, "..", N hexadecimal, + /* + * index line is N hexadecimal, "..", N hexadecimal, * and optional space with octal mode. */ const char *ptr, *eol; @@ -550,7 +556,8 @@ static const char *stop_at_slash(const char *line, int llen) return NULL; } -/* This is to extract the same name that appears on "diff --git" +/* + * This is to extract the same name that appears on "diff --git" * line. We do not find and return anything if it is a rename * patch, and it is OK because we will find the name elsewhere. * We need to reliably find name only when it is mode-change only, @@ -584,7 +591,8 @@ static char *git_header_name(char *line, int llen) goto free_and_fail1; strbuf_remove(&first, 0, cp + 1 - first.buf); - /* second points at one past closing dq of name. + /* + * second points at one past closing dq of name. * find the second name. */ while ((second < line + llen) && isspace(*second)) @@ -627,7 +635,8 @@ static char *git_header_name(char *line, int llen) return NULL; name++; - /* since the first name is unquoted, a dq if exists must be + /* + * since the first name is unquoted, a dq if exists must be * the beginning of the second name. */ for (second = name; second < line + llen; second++) { @@ -758,7 +767,7 @@ static int parse_num(const char *line, unsigned long *p) } static int parse_range(const char *line, int len, int offset, const char *expect, - unsigned long *p1, unsigned long *p2) + unsigned long *p1, unsigned long *p2) { int digits, ex; @@ -867,14 +876,14 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return offset; } - /** --- followed by +++ ? */ + /* --- followed by +++ ? */ if (memcmp("--- ", line, 4) || memcmp("+++ ", line + len, 4)) continue; /* * We only accept unified patches, so we want it to * at least have "@@ -a,b +c,d @@\n", which is 14 chars - * minimum + * minimum ("@@ -0,0 +1 @@\n" is the shortest). */ nextlen = linelen(line + len, size - len); if (size < nextlen + 14 || memcmp("@@ -", line + len + nextlen, 4)) @@ -889,7 +898,7 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc return -1; } -static void check_whitespace(const char *line, int len) +static void check_whitespace(const char *line, int len, unsigned ws_rule) { const char *err = "Adds trailing whitespace"; int seen_space = 0; @@ -901,23 +910,35 @@ static void check_whitespace(const char *line, int len) * this function. That is, an addition of an empty line would * check the '+' here. Sneaky... */ - if (isspace(line[len-2])) + if ((ws_rule & WS_TRAILING_SPACE) && isspace(line[len-2])) goto error; /* * Make sure that there is no space followed by a tab in * indentation. */ - err = "Space in indent is followed by a tab"; - for (i = 1; i < len; i++) { - if (line[i] == '\t') { - if (seen_space) - goto error; - } - else if (line[i] == ' ') - seen_space = 1; - else - break; + if (ws_rule & WS_SPACE_BEFORE_TAB) { + err = "Space in indent is followed by a tab"; + for (i = 1; i < len; i++) { + if (line[i] == '\t') { + if (seen_space) + goto error; + } + else if (line[i] == ' ') + seen_space = 1; + else + break; + } + } + + /* + * Make sure that the indentation does not contain more than + * 8 spaces. + */ + if ((ws_rule & WS_INDENT_WITH_NON_TAB) && + (8 < len) && !strncmp("+ ", line, 9)) { + err = "Indent more than 8 places with spaces"; + goto error; } return; @@ -931,14 +952,14 @@ static void check_whitespace(const char *line, int len) err, patch_input_file, linenr, len-2, line+1); } - /* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference * between a "---" that is part of a patch, and a "---" that starts * the next patch is to look at the line counts.. */ -static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment) +static int parse_fragment(char *line, unsigned long size, + struct patch *patch, struct fragment *fragment) { int added, deleted; int len = linelen(line, size), offset; @@ -979,22 +1000,23 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s break; case '-': if (apply_in_reverse && - new_whitespace != nowarn_whitespace) - check_whitespace(line, len); + ws_error_action != nowarn_ws_error) + check_whitespace(line, len, patch->ws_rule); deleted++; oldlines--; trailing = 0; break; case '+': if (!apply_in_reverse && - new_whitespace != nowarn_whitespace) - check_whitespace(line, len); + ws_error_action != nowarn_ws_error) + check_whitespace(line, len, patch->ws_rule); added++; newlines--; trailing = 0; break; - /* We allow "\ No newline at end of file". Depending + /* + * We allow "\ No newline at end of file". Depending * on locale settings when the patch was produced we * don't know what this line looks like. The only * thing we do know is that it begins with "\ ". @@ -1012,7 +1034,8 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s fragment->leading = leading; fragment->trailing = trailing; - /* If a fragment ends with an incomplete line, we failed to include + /* + * If a fragment ends with an incomplete line, we failed to include * it in the above loop because we hit oldlines == newlines == 0 * before seeing it. */ @@ -1140,7 +1163,8 @@ static struct fragment *parse_binary_hunk(char **buf_p, int *status_p, int *used_p) { - /* Expect a line that begins with binary patch method ("literal" + /* + * Expect a line that begins with binary patch method ("literal" * or "delta"), followed by the length of data before deflating. * a sequence of 'length-byte' followed by base-85 encoded data * should follow, terminated by a newline. @@ -1189,7 +1213,8 @@ static struct fragment *parse_binary_hunk(char **buf_p, size--; break; } - /* Minimum line is "A00000\n" which is 7-byte long, + /* + * Minimum line is "A00000\n" which is 7-byte long, * and the line length must be multiple of 5 plus 2. */ if ((llen < 7) || (llen-2) % 5) @@ -1240,7 +1265,8 @@ static struct fragment *parse_binary_hunk(char **buf_p, static int parse_binary(char *buffer, unsigned long size, struct patch *patch) { - /* We have read "GIT binary patch\n"; what follows is a line + /* + * We have read "GIT binary patch\n"; what follows is a line * that says the patch method (currently, either "literal" or * "delta") and the length of data before deflating; a * sequence of 'length-byte' followed by base-85 encoded data @@ -1270,7 +1296,8 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) if (reverse) used += used_1; else if (status) { - /* not having reverse hunk is not an error, but having + /* + * Not having reverse hunk is not an error, but having * a corrupt reverse hunk is. */ free((void*) forward->patch); @@ -1291,7 +1318,12 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) if (offset < 0) return offset; - patchsize = parse_single_patch(buffer + offset + hdrsize, size - offset - hdrsize, patch); + patch->ws_rule = whitespace_rule(patch->new_name + ? patch->new_name + : patch->old_name); + + patchsize = parse_single_patch(buffer + offset + hdrsize, + size - offset - hdrsize, patch); if (!patchsize) { static const char *binhdr[] = { @@ -1367,8 +1399,10 @@ static void reverse_patches(struct patch *p) } } -static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; -static const char minuses[]= "----------------------------------------------------------------------"; +static const char pluses[] = +"++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"; +static const char minuses[]= +"----------------------------------------------------------------------"; static void show_stats(struct patch *patch) { @@ -1437,7 +1471,9 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) } } -static int find_offset(const char *buf, unsigned long size, const char *fragment, unsigned long fragsize, int line, int *lines) +static int find_offset(const char *buf, unsigned long size, + const char *fragment, unsigned long fragsize, + int line, int *lines) { int i; unsigned long start, backwards, forwards; @@ -1536,9 +1572,11 @@ static void remove_last_line(const char **rbuf, int *rsize) *rsize = offset + 1; } -static int apply_line(char *output, const char *patch, int plen) +static int apply_line(char *output, const char *patch, int plen, + unsigned ws_rule) { - /* plen is number of bytes to be copied from patch, + /* + * plen is number of bytes to be copied from patch, * starting at patch+1 (patch[0] is '+'). Typically * patch[plen] is '\n', unless this is the incomplete * last line. @@ -1551,13 +1589,17 @@ static int apply_line(char *output, const char *patch, int plen) int need_fix_leading_space = 0; char *buf; - if ((new_whitespace != strip_whitespace) || !whitespace_error || + if ((ws_error_action != correct_ws_error) || !whitespace_error || *patch != '+') { memcpy(output, patch + 1, plen); return plen; } - if (1 < plen && isspace(patch[plen-1])) { + /* + * Strip trailing whitespace + */ + if ((ws_rule & WS_TRAILING_SPACE) && + (1 < plen && isspace(patch[plen-1]))) { if (patch[plen] == '\n') add_nl_to_tail = 1; plen--; @@ -1566,15 +1608,23 @@ static int apply_line(char *output, const char *patch, int plen) fixed = 1; } + /* + * Check leading whitespaces (indent) + */ for (i = 1; i < plen; i++) { char ch = patch[i]; if (ch == '\t') { last_tab_in_indent = i; - if (0 <= last_space_in_indent) + if ((ws_rule & WS_SPACE_BEFORE_TAB) && + 0 <= last_space_in_indent) + need_fix_leading_space = 1; + } else if (ch == ' ') { + last_space_in_indent = i; + if ((ws_rule & WS_INDENT_WITH_NON_TAB) && + last_tab_in_indent < 0 && + 8 <= i) need_fix_leading_space = 1; } - else if (ch == ' ') - last_space_in_indent = i; else break; } @@ -1582,10 +1632,21 @@ static int apply_line(char *output, const char *patch, int plen) buf = output; if (need_fix_leading_space) { int consecutive_spaces = 0; - /* between patch[1..last_tab_in_indent] strip the - * funny spaces, updating them to tab as needed. + int last = last_tab_in_indent + 1; + + if (ws_rule & WS_INDENT_WITH_NON_TAB) { + /* have "last" point at one past the indent */ + if (last_tab_in_indent < last_space_in_indent) + last = last_space_in_indent + 1; + else + last = last_tab_in_indent + 1; + } + + /* + * between patch[1..last], strip the funny spaces, + * updating them to tab as needed. */ - for (i = 1; i < last_tab_in_indent; i++, plen--) { + for (i = 1; i < last; i++, plen--) { char ch = patch[i]; if (ch != ' ') { consecutive_spaces = 0; @@ -1598,8 +1659,10 @@ static int apply_line(char *output, const char *patch, int plen) } } } + while (0 < consecutive_spaces--) + *output++ = ' '; fixed = 1; - i = last_tab_in_indent; + i = last; } else i = 1; @@ -1612,7 +1675,8 @@ static int apply_line(char *output, const char *patch, int plen) return output + plen - buf; } -static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int inaccurate_eof) +static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, + int inaccurate_eof, unsigned ws_rule) { int match_beginning, match_end; const char *patch = frag->patch; @@ -1671,7 +1735,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina case '+': if (first != '+' || !no_add) { int added = apply_line(new + newsize, patch, - plen); + plen, ws_rule); newsize += added; if (first == '+' && added == 1 && new[newsize-1] == '\n') @@ -1694,8 +1758,9 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina size -= len; } - if (inaccurate_eof && oldsize > 0 && old[oldsize - 1] == '\n' && - newsize > 0 && new[newsize - 1] == '\n') { + if (inaccurate_eof && + oldsize > 0 && old[oldsize - 1] == '\n' && + newsize > 0 && new[newsize - 1] == '\n') { oldsize--; newsize--; } @@ -1732,7 +1797,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina if (match_beginning && offset) offset = -1; if (offset >= 0) { - if (new_whitespace == strip_whitespace && + if (ws_error_action == correct_ws_error && (buf->len - oldsize - offset == 0)) /* end of file? */ newsize -= new_blank_lines_at_end; @@ -1757,9 +1822,10 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag, int ina match_beginning = match_end = 0; continue; } - /* Reduce the number of context lines - * Reduce both leading and trailing if they are equal - * otherwise just reduce the larger context. + /* + * Reduce the number of context lines; reduce both + * leading and trailing if they are equal otherwise + * just reduce the larger context. */ if (leading >= trailing) { remove_first_line(&oldlines, &oldsize); @@ -1819,7 +1885,8 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) const char *name = patch->old_name ? patch->old_name : patch->new_name; unsigned char sha1[20]; - /* For safety, we require patch index line to contain + /* + * For safety, we require patch index line to contain * full 40-byte textual SHA1 for old and new, at least for now. */ if (strlen(patch->old_sha1_prefix) != 40 || @@ -1830,7 +1897,8 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) "without full index line", name); if (patch->old_name) { - /* See if the old one matches what the patch + /* + * See if the old one matches what the patch * applies to. */ hash_sha1_file(buf->buf, buf->len, blob_type, sha1); @@ -1867,7 +1935,8 @@ static int apply_binary(struct strbuf *buf, struct patch *patch) /* XXX read_sha1_file NUL-terminates */ strbuf_attach(buf, result, size, size + 1); } else { - /* We have verified buf matches the preimage; + /* + * We have verified buf matches the preimage; * apply the patch data to it, which is stored * in the patch->fragments->{patch,size}. */ @@ -1889,12 +1958,14 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch) { struct fragment *frag = patch->fragments; const char *name = patch->old_name ? patch->old_name : patch->new_name; + unsigned ws_rule = patch->ws_rule; + unsigned inaccurate_eof = patch->inaccurate_eof; if (patch->is_binary) return apply_binary(buf, patch); while (frag) { - if (apply_one_fragment(buf, frag, patch->inaccurate_eof)) { + if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) { error("patch failed: %s:%ld", name, frag->oldpos); if (!apply_with_reject) return -1; @@ -2066,7 +2137,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch) if (new_name && prev_patch && 0 < prev_patch->is_delete && !strcmp(prev_patch->old_name, new_name)) - /* A type-change diff is always split into a patch to + /* + * A type-change diff is always split into a patch to * delete old, immediately followed by a patch to * create new (see diff.c::run_diff()); in such a case * it is Ok that the entry to be deleted by the @@ -2670,7 +2742,7 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof) offset += nr; } - if (whitespace_error && (new_whitespace == error_on_whitespace)) + if (whitespace_error && (ws_error_action == die_on_ws_error)) apply = 0; update_index = check_index && apply; @@ -2865,7 +2937,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) squelched, squelched == 1 ? "" : "s"); } - if (new_whitespace == error_on_whitespace) + if (ws_error_action == die_on_ws_error) die("%d line%s add%s whitespace errors.", whitespace_error, whitespace_error == 1 ? "" : "s", diff --git a/builtin-blame.c b/builtin-blame.c index c158d319dc..5466d01f9a 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -130,6 +130,14 @@ static void origin_decref(struct origin *o) } } +static void drop_origin_blob(struct origin *o) +{ + if (o->file.ptr) { + free(o->file.ptr); + o->file.ptr = NULL; + } +} + /* * Each group of lines is described by a blame_entry; it can be split * as we pass blame to the parents. They form a linked list in the @@ -380,6 +388,7 @@ static struct origin *find_origin(struct scoreboard *sb, } } diff_flush(&diff_opts); + diff_tree_release_paths(&diff_opts); if (porigin) { /* * Create a freestanding copy that is not part of @@ -436,6 +445,7 @@ static struct origin *find_rename(struct scoreboard *sb, } } diff_flush(&diff_opts); + diff_tree_release_paths(&diff_opts); return porigin; } @@ -1157,7 +1167,7 @@ static int find_copy_in_parent(struct scoreboard *sb, } } diff_flush(&diff_opts); - + diff_tree_release_paths(&diff_opts); return retval; } @@ -1274,8 +1284,13 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) } finish: - for (i = 0; i < MAXPARENT; i++) - origin_decref(parent_origin[i]); + for (i = 0; i < MAXPARENT; i++) { + if (parent_origin[i]) { + drop_origin_blob(parent_origin[i]); + origin_decref(parent_origin[i]); + } + } + drop_origin_blob(origin); } /* diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 88b0ab36eb..6610d18358 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -98,8 +98,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) strbuf_addf(&buffer, "parent %s\n", sha1_to_hex(parent_sha1[i])); /* Person/date information */ - strbuf_addf(&buffer, "author %s\n", git_author_info(1)); - strbuf_addf(&buffer, "committer %s\n", git_committer_info(1)); + strbuf_addf(&buffer, "author %s\n", git_author_info(IDENT_ERROR_ON_NO_NAME)); + strbuf_addf(&buffer, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (!encoding_is_utf8) strbuf_addf(&buffer, "encoding %s\n", git_commit_encoding); strbuf_addch(&buffer, '\n'); diff --git a/builtin-commit.c b/builtin-commit.c index 19297ac027..9cb7589ac6 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -497,7 +497,7 @@ static void determine_author_info(struct strbuf *sb) email = xstrndup(lb + 2, rb - (lb + 2)); } - strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, 1)); + strbuf_addf(sb, "author %s\n", fmt_ident(name, email, date, IDENT_ERROR_ON_NO_NAME)); } static int parse_and_validate_options(int argc, const char *argv[], @@ -660,12 +660,16 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.verbose_header = 1; rev.show_root_diff = 1; rev.commit_format = get_commit_format("format:%h: %s"); - rev.always_show_header = 1; + rev.always_show_header = 0; printf("Created %scommit ", initial_commit ? "initial " : ""); - log_tree_commit(&rev, commit); - printf("\n"); + if (!log_tree_commit(&rev, commit)) { + struct strbuf buf = STRBUF_INIT; + format_commit_message(commit, "%h: %s", &buf); + printf("%s\n", buf.buf); + strbuf_release(&buf); + } } int git_commit_config(const char *k, const char *v) @@ -776,7 +780,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } determine_author_info(&sb); - strbuf_addf(&sb, "committer %s\n", git_committer_info(1)); + strbuf_addf(&sb, "committer %s\n", git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (!is_encoding_utf8(git_commit_encoding)) strbuf_addf(&sb, "encoding %s\n", git_commit_encoding); strbuf_addch(&sb, '\n'); @@ -787,15 +791,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix) char index[PATH_MAX]; const char *env[2] = { index, NULL }; snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - launch_editor(git_path(commit_editmsg), &sb, env); - } else if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { - rollback_index_files(); - die("could not read commit message"); + launch_editor(git_path(commit_editmsg), NULL, env); } - if (run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { + if (!no_verify && + run_hook(index_file, "commit-msg", git_path(commit_editmsg))) { rollback_index_files(); exit(1); } + if (strbuf_read_file(&sb, git_path(commit_editmsg), 0) < 0) { + rollback_index_files(); + die("could not read commit message"); + } /* Truncate the message just before the diff, if any. */ p = strstr(sb.buf, "\ndiff --git a/"); diff --git a/builtin-fast-export.c b/builtin-fast-export.c index 2136aadfd7..ef27eee71b 100755 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -103,7 +103,7 @@ static void handle_object(const unsigned char *sha1) mark_object(object); printf("blob\nmark :%d\ndata %lu\n", last_idnum, size); - if (fwrite(buf, size, 1, stdout) != 1) + if (size && fwrite(buf, size, 1, stdout) != 1) die ("Could not write blob %s", sha1_to_hex(sha1)); printf("\n"); diff --git a/builtin-log.c b/builtin-log.c index d375c9dbf9..cc3cc9069a 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -554,7 +554,7 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha static void gen_message_id(char *dest, unsigned int length, char *base) { - const char *committer = git_committer_info(-1); + const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME); const char *email_start = strrchr(committer, '<'); const char *email_end = strrchr(committer, '>'); if(!email_start || !email_end || email_start > email_end - 1) @@ -662,7 +662,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) !strcmp(argv[i], "-s")) { const char *committer; const char *endpos; - committer = git_committer_info(1); + committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); endpos = strchr(committer, '>'); if (!endpos) die("bogos committer info %s\n", committer); diff --git a/builtin-ls-remote.c b/builtin-ls-remote.c index 56f3f88023..c2caeeabe2 100644 --- a/builtin-ls-remote.c +++ b/builtin-ls-remote.c @@ -6,6 +6,27 @@ static const char ls_remote_usage[] = "git-ls-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>"; +/* + * Is there one among the list of patterns that match the tail part + * of the path? + */ +static int tail_match(const char **pattern, const char *path) +{ + const char *p; + char pathbuf[PATH_MAX]; + + if (!pattern) + return 1; /* no restriction */ + + if (snprintf(pathbuf, sizeof(pathbuf), "/%s", path) > sizeof(pathbuf)) + return error("insanely long ref %.*s...", 20, path); + while ((p = *(pattern++)) != NULL) { + if (!fnmatch(p, pathbuf, 0)) + return 1; + } + return 0; +} + int cmd_ls_remote(int argc, const char **argv, const char *prefix) { int i; @@ -13,6 +34,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int nongit = 0; unsigned flags = 0; const char *uploadpack = NULL; + const char **pattern = NULL; struct remote *remote; struct transport *transport; @@ -47,12 +69,23 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) usage(ls_remote_usage); } dest = arg; + i++; break; } - if (!dest || i != argc - 1) + if (!dest) usage(ls_remote_usage); + if (argv[i]) { + int j; + pattern = xcalloc(sizeof(const char *), argc - i + 1); + for (j = i; j < argc; j++) { + int len = strlen(argv[j]); + char *p = xmalloc(len + 3); + sprintf(p, "*/%s", argv[j]); + pattern[j - i] = p; + } + } remote = nongit ? NULL : remote_get(dest); if (remote && !remote->url_nr) die("remote %s has no configured URL", dest); @@ -65,10 +98,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (!ref) return 1; - while (ref) { - if (check_ref_type(ref, flags)) - printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); - ref = ref->next; + for ( ; ref; ref = ref->next) { + if (!check_ref_type(ref, flags)) + continue; + if (!tail_match(pattern, ref->name)) + continue; + printf("%s %s\n", sha1_to_hex(ref->old_sha1), ref->name); } return 0; } diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 250dc56ab5..7dd0d7f826 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1709,6 +1709,16 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, list++; sub_size--; } + if (!sub_size) { + /* + * It is possible for some "paths" to have + * so many objects that no hash boundary + * might be found. Let's just steal the + * exact half in that case. + */ + sub_size = victim->remaining / 2; + list -= sub_size; + } target->list = list; victim->list_size -= sub_size; victim->remaining -= sub_size; diff --git a/builtin-reset.c b/builtin-reset.c index 4c61025aae..713c2d5346 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -158,6 +158,7 @@ static int read_from_tree(const char *prefix, const char **argv, return 1; diffcore_std(&opt); diff_flush(&opt); + diff_tree_release_paths(&opt); if (!index_was_discarded) /* The index is still clobbered from do_diff_cache() */ diff --git a/builtin-tag.c b/builtin-tag.c index 729389bbd6..274901a408 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -53,6 +53,8 @@ void launch_editor(const char *path, struct strbuf *buffer, const char *const *e die("There was a problem with the editor %s.", editor); } + if (!buffer) + return; if (strbuf_read_file(buffer, path, 0) < 0) die("could not read message file '%s': %s", path, strerror(errno)); @@ -186,7 +188,7 @@ static int do_sign(struct strbuf *buffer) int len; if (!*signingkey) { - if (strlcpy(signingkey, git_committer_info(1), + if (strlcpy(signingkey, git_committer_info(IDENT_ERROR_ON_NO_NAME), sizeof(signingkey)) > sizeof(signingkey) - 1) return error("committer info too long."); bracket = strchr(signingkey, '>'); @@ -234,14 +236,18 @@ static const char tag_template[] = "# Write a tag message\n" "#\n"; +static void set_signingkey(const char *value) +{ + if (strlcpy(signingkey, value, sizeof(signingkey)) >= sizeof(signingkey)) + die("signing key value too long (%.10s...)", value); +} + static int git_tag_config(const char *var, const char *value) { if (!strcmp(var, "user.signingkey")) { if (!value) die("user.signingkey without value"); - if (strlcpy(signingkey, value, sizeof(signingkey)) - >= sizeof(signingkey)) - die("user.signingkey value too long"); + set_signingkey(value); return 0; } @@ -296,7 +302,7 @@ static void create_tag(const unsigned char *object, const char *tag, sha1_to_hex(object), typename(type), tag, - git_committer_info(1)); + git_committer_info(IDENT_ERROR_ON_NO_NAME)); if (header_len > sizeof(header_buf) - 1) die("tag header too big."); @@ -394,6 +400,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, options, git_tag_usage, 0); + if (keyid) { + sign = 1; + set_signingkey(keyid); + } if (sign) annotate = 1; @@ -453,6 +453,9 @@ void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); enum date_mode parse_date_format(const char *format); +#define IDENT_WARN_ON_NO_NAME 1 +#define IDENT_ERROR_ON_NO_NAME 2 +#define IDENT_NO_DATE 4 extern const char *git_author_info(int); extern const char *git_committer_info(int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); @@ -605,7 +608,7 @@ extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char /* pager.c */ extern void setup_pager(void); extern char *pager_program; -extern int pager_in_use; +extern int pager_in_use(void); extern int pager_use_color; extern char *editor_program; @@ -641,6 +644,18 @@ extern int diff_auto_refresh_index; /* match-trees.c */ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, int); +/* + * whitespace rules. + * used by both diff and apply + */ +#define WS_TRAILING_SPACE 01 +#define WS_SPACE_BEFORE_TAB 02 +#define WS_INDENT_WITH_NON_TAB 04 +#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB) +extern unsigned whitespace_rule_cfg; +extern unsigned whitespace_rule(const char *); +extern unsigned parse_whitespace_rule(const char *); + /* ls-files */ int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); @@ -135,7 +135,7 @@ int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) auto_color: if (stdout_is_tty < 0) stdout_is_tty = isatty(1); - if (stdout_is_tty || (pager_in_use && pager_use_color)) { + if (stdout_is_tty || (pager_in_use() && pager_use_color)) { char *term = getenv("TERM"); if (term && strcmp(term, "dumb")) return 1; @@ -439,6 +439,11 @@ int git_default_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.whitespace")) { + whitespace_rule_cfg = parse_whitespace_rule(value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -646,13 +651,19 @@ static int store_write_pair(int fd, const char* key, const char* value) int length = strlen(key+store.baselen+1); int quote = 0; - /* Check to see if the value needs to be quoted. */ + /* + * Check to see if the value needs to be surrounded with a dq pair. + * Note that problematic characters are always backslash-quoted; this + * check is about not losing leading or trailing SP and strings that + * follow beginning-of-comment characters (i.e. ';' and '#') by the + * configuration parser. + */ if (value[0] == ' ') quote = 1; for (i = 0; value[i]; i++) if (value[i] == ';' || value[i] == '#') quote = 1; - if (value[i-1] == ' ') + if (i && value[i-1] == ' ') quote = 1; if (write_in_full(fd, "\t", 1) != 1 || diff --git a/config.mak.in b/config.mak.in index 7d5df9bf3c..15fb26cb92 100644 --- a/config.mak.in +++ b/config.mak.in @@ -23,6 +23,7 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH +ASCIIDOC8=@ASCIIDOC8@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ diff --git a/configure.ac b/configure.ac index dd4b4eb982..6f641e32f0 100644 --- a/configure.ac +++ b/configure.ac @@ -122,6 +122,27 @@ if test -z "$NO_TCLTK"; then AC_SUBST(TCLTK_PATH) fi fi +AC_CHECK_PROGS(ASCIIDOC, [asciidoc]) +if test -n "$ASCIIDOC"; then + AC_MSG_CHECKING([for asciidoc version]) + asciidoc_version=`$ASCIIDOC --version 2>&1` + case "${asciidoc_version}" in + asciidoc' '8*) + ASCIIDOC8=YesPlease + AC_MSG_RESULT([${asciidoc_version} > 7]) + ;; + asciidoc' '7*) + ASCIIDOC8= + AC_MSG_RESULT([${asciidoc_version}]) + ;; + *) + ASCIIDOC8= + AC_MSG_RESULT([${asciidoc_version} (unknown)]) + ;; + esac +fi +AC_SUBST(ASCIIDOC8) + ## Checks for libraries. AC_MSG_NOTICE([CHECKS for libraries]) diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index e147da0596..28a4899a0a 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -49,6 +49,7 @@ (eval-when-compile (require 'cl)) (require 'ewoc) (require 'log-edit) +(require 'easymenu) ;;;; Customizations @@ -1297,7 +1298,47 @@ Return the list of files that haven't been handled." (define-key toggle-map "i" 'git-toggle-show-ignored) (define-key toggle-map "k" 'git-toggle-show-unknown) (define-key toggle-map "m" 'git-toggle-all-marks) - (setq git-status-mode-map map))) + (setq git-status-mode-map map)) + (easy-menu-define git-menu git-status-mode-map + "Git Menu" + `("Git" + ["Refresh" git-refresh-status t] + ["Commit" git-commit-file t] + ("Merge" + ["Next Unmerged File" git-next-unmerged-file t] + ["Prev Unmerged File" git-prev-unmerged-file t] + ["Mark as Resolved" git-resolve-file t] + ["Interactive Merge File" git-find-file-imerge t] + ["Diff Against Common Base File" git-diff-file-base t] + ["Diff Combined" git-diff-file-combined t] + ["Diff Against Merge Head" git-diff-file-merge-head t] + ["Diff Against Mine" git-diff-file-mine t] + ["Diff Against Other" git-diff-file-other t]) + "--------" + ["Add File" git-add-file t] + ["Revert File" git-revert-file t] + ["Ignore File" git-ignore-file t] + ["Remove File" git-remove-file t] + "--------" + ["Find File" git-find-file t] + ["View File" git-view-file t] + ["Diff File" git-diff-file t] + ["Interactive Diff File" git-diff-file-idiff t] + ["Log" git-log-file t] + "--------" + ["Mark" git-mark-file t] + ["Mark All" git-mark-all t] + ["Unmark" git-unmark-file t] + ["Unmark All" git-unmark-all t] + ["Toggle All Marks" git-toggle-all-marks t] + ["Hide Handled Files" git-remove-handled t] + "--------" + ["Show Uptodate Files" git-toggle-show-uptodate :style toggle :selected git-show-uptodate] + ["Show Ignored Files" git-toggle-show-ignored :style toggle :selected git-show-ignored] + ["Show Unknown Files" git-toggle-show-unknown :style toggle :selected git-show-unknown] + "--------" + ["Quit" git-status-quit t]))) + ;; git mode should only run in the *git status* buffer (put 'git-status-mode 'mode-class 'special) @@ -454,6 +454,7 @@ static void diff_words_show(struct diff_words_data *diff_words) struct emit_callback { struct xdiff_emit_state xm; int nparents, color_diff; + unsigned ws_rule; const char **label_path; struct diff_words_data *diff_words; int *found_changesp; @@ -493,8 +494,8 @@ static void emit_line(const char *set, const char *reset, const char *line, int } static void emit_line_with_ws(int nparents, - const char *set, const char *reset, const char *ws, - const char *line, int len) + const char *set, const char *reset, const char *ws, + const char *line, int len, unsigned ws_rule) { int col0 = nparents; int last_tab_in_indent = -1; @@ -502,13 +503,17 @@ static void emit_line_with_ws(int nparents, int i; int tail = len; int need_highlight_leading_space = 0; - /* The line is a newly added line. Does it have funny leading - * whitespaces? In indent, SP should never precede a TAB. + /* + * The line is a newly added line. Does it have funny leading + * whitespaces? In indent, SP should never precede a TAB. In + * addition, under "indent with non tab" rule, there should not + * be more than 8 consecutive spaces. */ for (i = col0; i < len; i++) { if (line[i] == '\t') { last_tab_in_indent = i; - if (0 <= last_space_in_indent) + if ((ws_rule & WS_SPACE_BEFORE_TAB) && + 0 <= last_space_in_indent) need_highlight_leading_space = 1; } else if (line[i] == ' ') @@ -516,6 +521,13 @@ static void emit_line_with_ws(int nparents, else break; } + if ((ws_rule & WS_INDENT_WITH_NON_TAB) && + 0 <= last_space_in_indent && + last_tab_in_indent < 0 && + 8 <= (i - col0)) { + last_tab_in_indent = i; + need_highlight_leading_space = 1; + } fputs(set, stdout); fwrite(line, col0, 1, stdout); fputs(reset, stdout); @@ -540,10 +552,12 @@ static void emit_line_with_ws(int nparents, tail = len - 1; if (line[tail] == '\n' && i < tail) tail--; - while (i < tail) { - if (!isspace(line[tail])) - break; - tail--; + if (ws_rule & WS_TRAILING_SPACE) { + while (i < tail) { + if (!isspace(line[tail])) + break; + tail--; + } } if ((i < tail && line[tail + 1] != '\n')) { /* This has whitespace between tail+1..len */ @@ -565,7 +579,7 @@ static void emit_add_line(const char *reset, struct emit_callback *ecbdata, cons emit_line(set, reset, line, len); else emit_line_with_ws(ecbdata->nparents, set, reset, ws, - line, len); + line, len, ecbdata->ws_rule); } static void fn_out_consume(void *priv, char *line, unsigned long len) @@ -720,7 +734,9 @@ struct diffstat_t { int nr; int alloc; struct diffstat_file { + char *from_name; char *name; + char *print_name; unsigned is_unmerged:1; unsigned is_binary:1; unsigned is_renamed:1; @@ -741,11 +757,14 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, } diffstat->files[diffstat->nr++] = x; if (name_b) { - x->name = pprint_rename(name_a, name_b); + x->from_name = xstrdup(name_a); + x->name = xstrdup(name_b); x->is_renamed = 1; } - else + else { + x->from_name = NULL; x->name = xstrdup(name_a); + } return x; } @@ -789,6 +808,28 @@ static void show_graph(char ch, int cnt, const char *set, const char *reset) printf("%s", reset); } +static void fill_print_name(struct diffstat_file *file) +{ + char *pname; + + if (file->print_name) + return; + + if (!file->is_renamed) { + struct strbuf buf; + strbuf_init(&buf, 0); + if (quote_c_style(file->name, &buf, NULL, 0)) { + pname = strbuf_detach(&buf, NULL); + } else { + pname = file->name; + strbuf_release(&buf); + } + } else { + pname = pprint_rename(file->from_name, file->name); + } + file->print_name = pname; +} + static void show_stats(struct diffstat_t* data, struct diff_options *options) { int i, len, add, del, total, adds = 0, dels = 0; @@ -822,19 +863,8 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; int change = file->added + file->deleted; - - if (!file->is_renamed) { /* renames are already quoted by pprint_rename */ - struct strbuf buf; - strbuf_init(&buf, 0); - if (quote_c_style(file->name, &buf, NULL, 0)) { - free(file->name); - file->name = strbuf_detach(&buf, NULL); - } else { - strbuf_release(&buf); - } - } - - len = strlen(file->name); + fill_print_name(file); + len = strlen(file->print_name); if (max_len < len) max_len = len; @@ -859,7 +889,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) for (i = 0; i < data->nr; i++) { const char *prefix = ""; - char *name = data->files[i]->name; + char *name = data->files[i]->print_name; int added = data->files[i]->added; int deleted = data->files[i]->deleted; int name_len; @@ -887,17 +917,17 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) printf("%s%d%s", add_c, added, reset); printf(" bytes"); printf("\n"); - goto free_diffstat_file; + continue; } else if (data->files[i]->is_unmerged) { show_name(prefix, name, len, reset, set); printf(" Unmerged\n"); - goto free_diffstat_file; + continue; } else if (!data->files[i]->is_renamed && (added + deleted == 0)) { total_files--; - goto free_diffstat_file; + continue; } /* @@ -919,11 +949,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) show_graph('+', add, add_c, reset); show_graph('-', del, del_c, reset); putchar('\n'); - free_diffstat_file: - free(data->files[i]->name); - free(data->files[i]); } - free(data->files); printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n", set, total_files, adds, dels, reset); } @@ -948,11 +974,7 @@ static void show_shortstats(struct diffstat_t* data) dels += deleted; } } - free(data->files[i]->name); - free(data->files[i]); } - free(data->files); - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", total_files, adds, dels); } @@ -961,6 +983,9 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) { int i; + if (data->nr == 0) + return; + for (i = 0; i < data->nr; i++) { struct diffstat_file *file = data->files[i]; @@ -968,19 +993,44 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options) printf("-\t-\t"); else printf("%d\t%d\t", file->added, file->deleted); - if (!file->is_renamed) { - write_name_quoted(file->name, stdout, options->line_termination); + if (options->line_termination) { + fill_print_name(file); + if (!file->is_renamed) + write_name_quoted(file->name, stdout, + options->line_termination); + else { + fputs(file->print_name, stdout); + putchar(options->line_termination); + } } else { - fputs(file->name, stdout); - putchar(options->line_termination); + if (file->is_renamed) { + putchar('\0'); + write_name_quoted(file->from_name, stdout, '\0'); + } + write_name_quoted(file->name, stdout, '\0'); } } } +static void free_diffstat_info(struct diffstat_t *diffstat) +{ + int i; + for (i = 0; i < diffstat->nr; i++) { + struct diffstat_file *f = diffstat->files[i]; + if (f->name != f->print_name) + free(f->print_name); + free(f->name); + free(f->from_name); + free(f); + } + free(diffstat->files); +} + struct checkdiff_t { struct xdiff_emit_state xm; const char *filename; int lineno, color_diff; + unsigned ws_rule; }; static void checkdiff_consume(void *priv, char *line, unsigned long len) @@ -994,13 +1044,20 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) int i, spaces = 0, space_before_tab = 0, white_space_at_end = 0; /* check space before tab */ - for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++) + for (i = 1; i < len; i++) { if (line[i] == ' ') spaces++; - if (line[i - 1] == '\t' && spaces) - space_before_tab = 1; + else if (line[i] == '\t') { + if (spaces) { + space_before_tab = 1; + break; + } + } + else + break; + } - /* check white space at line end */ + /* check whitespace at line end */ if (line[len - 1] == '\n') len--; if (isspace(line[len - 1])) @@ -1014,9 +1071,10 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len) putchar(','); } if (white_space_at_end) - printf("white space at end"); + printf("whitespace at end"); printf(":%s ", reset); - emit_line_with_ws(1, set, reset, ws, line, len); + emit_line_with_ws(1, set, reset, ws, line, len, + data->ws_rule); } data->lineno++; @@ -1317,6 +1375,7 @@ static void builtin_diff(const char *name_a, ecbdata.label_path = lbl; ecbdata.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); ecbdata.found_changesp = &o->found_changes; + ecbdata.ws_rule = whitespace_rule(name_b ? name_b : name_a); xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; xecfg.flags = XDL_EMIT_FUNCNAMES; @@ -1410,6 +1469,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.filename = name_b ? name_b : name_a; data.lineno = 0; data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF); + data.ws_rule = whitespace_rule(data.filename); if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); @@ -2925,8 +2985,9 @@ void diff_flush(struct diff_options *options) show_numstat(&diffstat, options); if (output_format & DIFF_FORMAT_DIFFSTAT) show_stats(&diffstat, options); - else if (output_format & DIFF_FORMAT_SHORTSTAT) + if (output_format & DIFF_FORMAT_SHORTSTAT) show_shortstats(&diffstat); + free_diffstat_info(&diffstat); separator++; } diff --git a/environment.c b/environment.c index 1dab72ec15..18a1c4eec4 100644 --- a/environment.c +++ b/environment.c @@ -31,11 +31,11 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 16 * 1024 * 1024; char *pager_program; -int pager_in_use; int pager_use_color = 1; char *editor_program; char *excludes_file; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ +unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; /* This is set by setup_git_dir_gently() and/or git_default_config() */ char *git_work_tree_cfg; diff --git a/git-checkout.sh b/git-checkout.sh index f6d58ac044..5621c69d86 100755 --- a/git-checkout.sh +++ b/git-checkout.sh @@ -2,13 +2,13 @@ OPTIONS_KEEPDASHDASH=t OPTIONS_SPEC="\ -git-branch [options] [<branch>] [<paths>...] +git-checkout [options] [<branch>] [<paths>...] -- b= create a new branch started at <branch> -l create the new branchs reflog -track tells if the new branch should track the remote branch +l create the new branch's reflog +track arrange that the new branch tracks the remote branch f proceed even if the index or working tree is not HEAD -m performa three-way merge on local modifications if needed +m merge local modifications into the new branch q,quiet be quiet " SUBDIRECTORY_OK=Sometimes diff --git a/git-clone.sh b/git-clone.sh index ecf9d89a10..68085a3225 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -205,7 +205,10 @@ fi # it is local if base=$(get_repo_base "$repo"); then repo="$base" - local=yes + if test -z "$depth" + then + local=yes + fi fi dir="$2" @@ -297,7 +300,8 @@ yes) find objects -type f -print | sed -e 1q) # objects directory should not be empty because # we are cloning! - test -f "$repo/$sample_file" || exit + test -f "$repo/$sample_file" || + die "fatal: cannot clone empty repository" if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null then rm -f "$GIT_DIR/objects/sample" diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index 1e7727d276..9ee3f80452 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -80,6 +80,10 @@ case "${1:-.}${2:-.}${3:-.}" in echo "ERROR: $4: Not merging symbolic link changes." exit 1 ;; + *,160000,*) + echo "ERROR: $4: Not merging conflicting submodule changes." + exit 1 + ;; esac src2=`git-unpack-file $3` diff --git a/git-mergetool.sh b/git-mergetool.sh index 5587c5ecea..2f31fa2417 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -152,10 +152,11 @@ merge_file () { exit 1 fi - BACKUP="$path.BACKUP.$$" - LOCAL="$path.LOCAL.$$" - REMOTE="$path.REMOTE.$$" - BASE="$path.BASE.$$" + ext="$$$(expr "$path" : '.*\(\.[^/]*\)$')" + BACKUP="$path.BACKUP.$ext" + LOCAL="$path.LOCAL.$ext" + REMOTE="$path.REMOTE.$ext" + BASE="$path.BASE.$ext" mv -- "$path" "$BACKUP" cp -- "$BACKUP" "$path" diff --git a/git-send-email.perl b/git-send-email.perl index 76baa8e431..1d6f466453 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -367,8 +367,10 @@ if ($thread && !defined $initial_reply_to && $prompting) { } while (!defined $_); $initial_reply_to = $_; - $initial_reply_to =~ s/^\s+<?/</; - $initial_reply_to =~ s/>?\s+$/>/; +} +if (defined $initial_reply_to && $_ ne "") { + $initial_reply_to =~ s/^\s*<?/</; + $initial_reply_to =~ s/>?\s*$/>/; } if (!defined $smtp_server) { diff --git a/git-svn.perl b/git-svn.perl index 9f884eb213..54d784469a 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -529,7 +529,7 @@ sub cmd_find_rev { "$head history\n"; } my $desired_revision = substr($revision_or_hash, 1); - $result = $gs->rev_db_get($desired_revision); + $result = $gs->rev_map_get($desired_revision); } else { my (undef, $rev, undef) = cmt_metadata($revision_or_hash); $result = $rev; @@ -1128,12 +1128,12 @@ sub working_head_info { if (defined $url && defined $rev) { next if $max{$url} and $max{$url} < $rev; if (my $gs = Git::SVN->find_by_url($url)) { - my $c = $gs->rev_db_get($rev); + my $c = $gs->rev_map_get($rev); if ($c && $c eq $hash) { close $fh; # break the pipe return ($url, $rev, $uuid, $gs); } else { - $max{$url} ||= $gs->rev_db_max; + $max{$url} ||= $gs->rev_map_max; } } } @@ -1234,6 +1234,8 @@ sub md5sum { package Git::SVN; use strict; use warnings; +use Fcntl qw/:DEFAULT :seek/; +use constant rev_map_fmt => 'NH40'; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head $_use_svnsync_props $no_reuse_existing $_minimize_url @@ -1362,7 +1364,7 @@ sub fetch_all { if ($fetch) { foreach my $p (sort keys %$fetch) { my $gs = Git::SVN->new($fetch->{$p}, $repo_id, $p); - my $lr = $gs->rev_db_max; + my $lr = $gs->rev_map_max; if (defined $lr) { $base = $lr if ($lr < $base); } @@ -1897,38 +1899,20 @@ sub last_rev_commit { return ($rev, $c); } } - my $db_path = $self->db_path; - unless (-e $db_path) { + my $map_path = $self->map_path; + unless (-e $map_path) { ($self->{last_rev}, $self->{last_commit}) = (undef, undef); return (undef, undef); } - my $offset = -41; # from tail - my $rl; - open my $fh, '<', $db_path or croak "$db_path not readable: $!\n"; - sysseek($fh, $offset, 2); # don't care for errors - sysread($fh, $rl, 41) == 41 or return (undef, undef); - chomp $rl; - while (('0' x40) eq $rl && sysseek($fh, 0, 1) != 0) { - $offset -= 41; - sysseek($fh, $offset, 2); # don't care for errors - sysread($fh, $rl, 41) == 41 or return (undef, undef); - chomp $rl; - } - if ($c && $c ne $rl) { - die "$db_path and ", $self->refname, - " inconsistent!:\n$c != $rl\n"; - } - my $rev = sysseek($fh, 0, 1) or croak $!; - $rev = ($rev - 41) / 41; - close $fh or croak $!; - ($self->{last_rev}, $self->{last_commit}) = ($rev, $c); - return ($rev, $c); + my ($rev, $commit) = $self->rev_map_max(1); + ($self->{last_rev}, $self->{last_commit}) = ($rev, $commit); + return ($rev, $commit); } sub get_fetch_range { my ($self, $min, $max) = @_; $max ||= $self->ra->get_latest_revnum; - $min ||= $self->rev_db_max; + $min ||= $self->rev_map_max; (++$min, $max); } @@ -2073,7 +2057,7 @@ sub do_git_commit { " was r$lr, but we are about to fetch: ", "r$log_entry->{revision}!\n"; } - if (my $c = $self->rev_db_get($log_entry->{revision})) { + if (my $c = $self->rev_map_get($log_entry->{revision})) { croak "$log_entry->{revision} = $c already exists! ", "Why are we refetching it?\n"; } @@ -2116,14 +2100,14 @@ sub do_git_commit { die "Failed to commit, invalid sha1: $commit\n"; } - $self->rev_db_set($log_entry->{revision}, $commit, 1); + $self->rev_map_set($log_entry->{revision}, $commit, 1); $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; print "r$log_entry->{revision}"; if (defined $log_entry->{svm_revision}) { print " (\@$log_entry->{svm_revision})"; - $self->rev_db_set($log_entry->{svm_revision}, $commit, + $self->rev_map_set($log_entry->{svm_revision}, $commit, 0, $self->svm_uuid); } print " = $commit ($self->{ref_id})\n"; @@ -2465,25 +2449,44 @@ sub set_tree { } } +sub rebuild_from_rev_db { + my ($self, $path) = @_; + my $r = -1; + open my $fh, '<', $path or croak "open: $!"; + while (<$fh>) { + length($_) == 41 or croak "inconsistent size in ($_) != 41"; + chomp($_); + ++$r; + next if $_ eq ('0' x 40); + $self->rev_map_set($r, $_); + print "r$r = $_\n"; + } + close $fh or croak "close: $!"; + unlink $path or croak "unlink: $!"; +} + sub rebuild { my ($self) = @_; - my $db_path = $self->db_path; - return if (-e $db_path && ! -z $db_path); + my $map_path = $self->map_path; + return if (-e $map_path && ! -z $map_path); return unless ::verify_ref($self->refname.'^0'); - if (-f $self->{db_root}) { - rename $self->{db_root}, $db_path or die - "rename $self->{db_root} => $db_path failed: $!\n"; - my ($dir, $base) = ($db_path =~ m#^(.*?)/?([^/]+)$#); - symlink $base, $self->{db_root} or die - "symlink $base => $self->{db_root} failed: $!\n"; + if ($self->use_svm_props || $self->no_metadata) { + my $rev_db = $self->rev_db_path; + $self->rebuild_from_rev_db($rev_db); + if ($self->use_svm_props) { + my $svm_rev_db = $self->rev_db_path($self->svm_uuid); + $self->rebuild_from_rev_db($svm_rev_db); + } + $self->unlink_rev_db_symlink; return; } - print "Rebuilding $db_path ...\n"; - my ($log, $ctx) = command_output_pipe("log", '--no-color', $self->refname); - my $latest; + print "Rebuilding $map_path ...\n"; + my ($log, $ctx) = + command_output_pipe(qw/rev-list --pretty=raw --no-color --reverse/, + $self->refname, '--'); my $full_url = $self->full_url; remove_username($full_url); - my $svn_uuid; + my $svn_uuid = $self->ra_uuid; my $c; while (<$log>) { if ( m{^commit ($::sha1)$} ) { @@ -2499,46 +2502,85 @@ sub rebuild { # if we merged or otherwise started elsewhere, this is # how we break out of it - if ((defined $svn_uuid && ($uuid ne $svn_uuid)) || + if (($uuid ne $svn_uuid) || ($full_url && $url && ($url ne $full_url))) { next; } - $latest ||= $rev; - $svn_uuid ||= $uuid; - $self->rev_db_set($rev, $c); + $self->rev_map_set($rev, $c); print "r$rev = $c\n"; } command_close_pipe($log, $ctx); - print "Done rebuilding $db_path\n"; + print "Done rebuilding $map_path\n"; + my $rev_db_path = $self->rev_db_path; + if (-f $self->rev_db_path) { + unlink $self->rev_db_path or croak "unlink: $!"; + } + $self->unlink_rev_db_symlink; } -# rev_db: +# rev_map: # Tie::File seems to be prone to offset errors if revisions get sparse, # it's not that fast, either. Tie::File is also not in Perl 5.6. So # one of my favorite modules is out :< Next up would be one of the DBM -# modules, but I'm not sure which is most portable... So I'll just -# go with something that's plain-text, but still capable of -# being randomly accessed. So here's my ultra-simple fixed-width -# database. All records are 40 characters + "\n", so it's easy to seek -# to a revision: (41 * rev) is the byte offset. -# A record of 40 0s denotes an empty revision. -# And yes, it's still pretty fast (faster than Tie::File). +# modules, but I'm not sure which is most portable... +# +# This is the replacement for the rev_db format, which was too big +# and inefficient for large repositories with a lot of sparse history +# (mainly tags) +# +# The format is this: +# - 24 bytes for every record, +# * 4 bytes for the integer representing an SVN revision number +# * 20 bytes representing the sha1 of a git commit +# - No empty padding records like the old format +# (except the last record, which can be overwritten) +# - new records are written append-only since SVN revision numbers +# increase monotonically +# - lookups on SVN revision number are done via a binary search +# - Piping the file to xxd -c24 is a good way of dumping it for +# viewing or editing (piped back through xxd -r), should the need +# ever arise. +# - The last record can be padding revision with an all-zero sha1 +# This is used to optimize fetch performance when using multiple +# "fetch" directives in .git/config +# # These files are disposable unless noMetadata or useSvmProps is set -sub _rev_db_set { +sub _rev_map_set { my ($fh, $rev, $commit) = @_; - my $offset = $rev * 41; - # assume that append is the common case: - seek $fh, 0, 2 or croak $!; - my $pos = tell $fh; - if ($pos < $offset) { - for (1 .. (($offset - $pos) / 41)) { - print $fh (('0' x 40),"\n") or croak $!; + + my $size = (stat($fh))[7]; + ($size % 24) == 0 or croak "inconsistent size: $size"; + + my $wr_offset = 0; + if ($size > 0) { + sysseek($fh, -24, SEEK_END) or croak "seek: $!"; + my $read = sysread($fh, my $buf, 24) or croak "read: $!"; + $read == 24 or croak "read only $read bytes (!= 24)"; + my ($last_rev, $last_commit) = unpack(rev_map_fmt, $buf); + if ($last_commit eq ('0' x40)) { + if ($size >= 48) { + sysseek($fh, -48, SEEK_END) or croak "seek: $!"; + $read = sysread($fh, $buf, 24) or + croak "read: $!"; + $read == 24 or + croak "read only $read bytes (!= 24)"; + ($last_rev, $last_commit) = + unpack(rev_map_fmt, $buf); + if ($last_commit eq ('0' x40)) { + croak "inconsistent .rev_map\n"; + } + } + if ($last_rev >= $rev) { + croak "last_rev is higher!: $last_rev >= $rev"; + } + $wr_offset = -24; } } - seek $fh, $offset, 0 or croak $!; - print $fh $commit,"\n" or croak $!; + sysseek($fh, $wr_offset, SEEK_END) or croak "seek: $!"; + syswrite($fh, pack(rev_map_fmt, $rev, $commit), 24) == 24 or + croak "write: $!"; } sub mkfile { @@ -2551,10 +2593,10 @@ sub mkfile { } } -sub rev_db_set { +sub rev_map_set { my ($self, $rev, $commit, $update_ref, $uuid) = @_; length $commit == 40 or die "arg3 must be a full SHA1 hexsum\n"; - my $db = $self->db_path($uuid); + my $db = $self->map_path($uuid); my $db_lock = "$db.lock"; my $sig; if ($update_ref) { @@ -2569,16 +2611,18 @@ sub rev_db_set { # and we can't afford to lose it because rebuild() won't work if ($self->use_svm_props || $self->no_metadata) { $sync = 1; - copy($db, $db_lock) or die "rev_db_set(@_): ", + copy($db, $db_lock) or die "rev_map_set(@_): ", "Failed to copy: ", "$db => $db_lock ($!)\n"; } else { - rename $db, $db_lock or die "rev_db_set(@_): ", + rename $db, $db_lock or die "rev_map_set(@_): ", "Failed to rename: ", "$db => $db_lock ($!)\n"; } - open my $fh, '+<', $db_lock or die "Couldn't open $db_lock: $!\n"; - _rev_db_set($fh, $rev, $commit); + + sysopen(my $fh, $db_lock, O_RDWR | O_CREAT) + or croak "Couldn't open $db_lock: $!\n"; + _rev_map_set($fh, $rev, $commit); if ($sync) { $fh->flush or die "Couldn't flush $db_lock: $!\n"; $fh->sync or die "Couldn't sync $db_lock: $!\n"; @@ -2589,7 +2633,7 @@ sub rev_db_set { command_noisy('update-ref', '-m', "r$rev", $self->refname, $commit); } - rename $db_lock, $db or die "rev_db_set(@_): ", "Failed to rename: ", + rename $db_lock, $db or die "rev_map_set(@_): ", "Failed to rename: ", "$db_lock => $db ($!)\n"; delete $LOCKFILES{$db_lock}; if ($update_ref) { @@ -2599,29 +2643,76 @@ sub rev_db_set { } } -sub rev_db_max { - my ($self) = @_; +# If want_commit, this will return an array of (rev, commit) where +# commit _must_ be a valid commit in the archive. +# Otherwise, it'll return the max revision (whether or not the +# commit is valid or just a 0x40 placeholder). +sub rev_map_max { + my ($self, $want_commit) = @_; $self->rebuild; - my $db_path = $self->db_path; - my @stat = stat $db_path or return 0; - ($stat[7] % 41) == 0 or die "$db_path inconsistent size: $stat[7]\n"; - my $max = $stat[7] / 41; - (($max > 0) ? $max - 1 : 0); + my $map_path = $self->map_path; + stat $map_path or return $want_commit ? (0, undef) : 0; + sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!"; + my $size = (stat($fh))[7]; + ($size % 24) == 0 or croak "inconsistent size: $size"; + + if ($size == 0) { + close $fh or croak "close: $!"; + return $want_commit ? (0, undef) : 0; + } + + sysseek($fh, -24, SEEK_END) or croak "seek: $!"; + sysread($fh, my $buf, 24) == 24 or croak "read: $!"; + my ($r, $c) = unpack(rev_map_fmt, $buf); + if ($want_commit && $c eq ('0' x40)) { + if ($size < 48) { + return $want_commit ? (0, undef) : 0; + } + sysseek($fh, -48, SEEK_END) or croak "seek: $!"; + sysread($fh, $buf, 24) == 24 or croak "read: $!"; + ($r, $c) = unpack(rev_map_fmt, $buf); + if ($c eq ('0'x40)) { + croak "Penultimate record is all-zeroes in $map_path"; + } + } + close $fh or croak "close: $!"; + $want_commit ? ($r, $c) : $r; } -sub rev_db_get { +sub rev_map_get { my ($self, $rev, $uuid) = @_; - my $ret; - my $offset = $rev * 41; - my $db_path = $self->db_path($uuid); - return undef unless -e $db_path; - open my $fh, '<', $db_path or croak $!; - if (sysseek($fh, $offset, 0) == $offset) { - my $read = sysread($fh, $ret, 40); - $ret = undef if ($read != 40 || $ret eq ('0'x40)); + my $map_path = $self->map_path($uuid); + return undef unless -e $map_path; + + sysopen(my $fh, $map_path, O_RDONLY) or croak "open: $!"; + my $size = (stat($fh))[7]; + ($size % 24) == 0 or croak "inconsistent size: $size"; + + if ($size == 0) { + close $fh or croak "close: $fh"; + return undef; } - close $fh or croak $!; - $ret; + + my ($l, $u) = (0, $size - 24); + my ($r, $c, $buf); + + while ($l <= $u) { + my $i = int(($l/24 + $u/24) / 2) * 24; + sysseek($fh, $i, SEEK_SET) or croak "seek: $!"; + sysread($fh, my $buf, 24) == 24 or croak "read: $!"; + my ($r, $c) = unpack('NH40', $buf); + + if ($r < $rev) { + $l = $i + 24; + } elsif ($r > $rev) { + $u = $i - 24; + } else { # $r == $rev + close($fh) or croak "close: $!"; + return $c eq ('0' x 40) ? undef : $c; + } + } + close($fh) or croak "close: $!"; + undef; } # Finds the first svn revision that exists on (if $eq_ok is true) or @@ -2633,7 +2724,7 @@ sub find_rev_before { --$rev unless $eq_ok; $min_rev ||= 1; while ($rev >= $min_rev) { - if (my $c = $self->rev_db_get($rev)) { + if (my $c = $self->rev_map_get($rev)) { return ($rev, $c); } --$rev; @@ -2648,9 +2739,9 @@ sub find_rev_before { sub find_rev_after { my ($self, $rev, $eq_ok, $max_rev) = @_; ++$rev unless $eq_ok; - $max_rev ||= $self->rev_db_max(); + $max_rev ||= $self->rev_map_max; while ($rev <= $max_rev) { - if (my $c = $self->rev_db_get($rev)) { + if (my $c = $self->rev_map_get($rev)) { return ($rev, $c); } ++$rev; @@ -2673,13 +2764,32 @@ sub _new { bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", - db_root => "$dir/.rev_db", repo_id => $repo_id }, $class; + map_root => "$dir/.rev_map", repo_id => $repo_id }, $class; } -sub db_path { +# for read-only access of old .rev_db formats +sub unlink_rev_db_symlink { + my ($self) = @_; + my $link = $self->rev_db_path; + $link =~ s/\.[\w-]+$// or croak "missing UUID at the end of $link"; + if (-l $link) { + unlink $link or croak "unlink: $link failed!"; + } +} + +sub rev_db_path { + my ($self, $uuid) = @_; + my $db_path = $self->map_path($uuid); + $db_path =~ s{/\.rev_map\.}{/\.rev_db\.} + or croak "map_path: $db_path does not contain '/.rev_map.' !"; + $db_path; +} + +# the new replacement for .rev_db +sub map_path { my ($self, $uuid) = @_; $uuid ||= $self->ra_uuid; - "$self->{db_root}.$uuid"; + "$self->{map_root}.$uuid"; } sub uri_encode { @@ -3763,7 +3873,7 @@ sub gs_fetch_loop_common { foreach my $gs ($self->match_globs(\%exists, $paths, $globs, $r)) { - if ($gs->rev_db_max >= $r) { + if ($gs->rev_map_max >= $r) { next; } next unless $gs->match_paths($paths, $r); @@ -3792,8 +3902,9 @@ sub gs_fetch_loop_common { # pre-fill the .rev_db since it'll eventually get filled in # with '0' x40 if something new gets committed foreach my $gs (@$gsv) { - next if defined $gs->rev_db_get($max); - $gs->rev_db_set($max, 0 x40); + next if $gs->rev_map_max >= $max; + next if defined $gs->rev_map_get($max); + $gs->rev_map_set($max, 0 x40); } foreach my $g (@$globs) { my $k = "svn-remote.$g->{remote}.$g->{t}-maxRev"; @@ -3969,39 +4080,7 @@ sub cmt_showable { } sub log_use_color { - return 1 if $color; - my ($dc, $dcvar); - $dcvar = 'color.diff'; - $dc = `git-config --get $dcvar`; - if ($dc eq '') { - # nothing at all; fallback to "diff.color" - $dcvar = 'diff.color'; - $dc = `git-config --get $dcvar`; - } - chomp($dc); - if ($dc eq 'auto') { - my $pc; - $pc = `git-config --get color.pager`; - if ($pc eq '') { - # does not have it -- fallback to pager.color - $pc = `git-config --bool --get pager.color`; - } - else { - $pc = `git-config --bool --get color.pager`; - if ($?) { - $pc = 'false'; - } - } - chomp($pc); - if (-t *STDOUT || (defined $pager && $pc eq 'true')) { - return ($ENV{TERM} && $ENV{TERM} ne 'dumb'); - } - return 0; - } - return 0 if $dc eq 'never'; - return 1 if $dc eq 'always'; - chomp($dc = `git-config --bool --get $dcvar`); - return ($dc eq 'true'); + return $color || Git->repository->get_colorbool('color.diff'); } sub git_svn_log_cmd { @@ -4030,7 +4109,7 @@ sub git_svn_log_cmd { push @cmd, @log_opts; if (defined $r_max && $r_max == $r_min) { push @cmd, '--max-count=1'; - if (my $c = $gs->rev_db_get($r_max)) { + if (my $c = $gs->rev_map_get($r_max)) { push @cmd, $c; } } elsif (defined $r_max) { @@ -4060,6 +4139,7 @@ sub config_pager { } elsif (length $pager == 0 || $pager eq 'cat') { $pager = undef; } + $ENV{GIT_PAGER_IN_USE} = defined($pager); } sub run_pager { @@ -4311,6 +4391,16 @@ package Git::SVN::Migration; # --use-separate-remotes option in git-clone (now default) # - we do not automatically migrate to this (following # the example set by core git) +# +# v5 layout: .rev_db.$UUID => .rev_map.$UUID +# - newer, more-efficient format that uses 24-bytes per record +# with no filler space. +# - use xxd -c24 < .rev_map.$UUID to view and debug +# - This is a one-way migration, repositories updated to the +# new format will not be able to use old git-svn without +# rebuilding the .rev_db. Rebuilding the rev_db is not +# possible if noMetadata or useSvmProps are set; but should +# be no problem for users that use the (sensible) defaults. use strict; use warnings; use Carp qw/croak/; diff --git a/http-push.c b/http-push.c index 78283b4de3..fffbe9ccb4 100644 --- a/http-push.c +++ b/http-push.c @@ -1275,8 +1275,6 @@ static struct remote_lock *lock_remote(const char *path, long timeout) char *ep; char timeout_header[25]; struct remote_lock *lock = NULL; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; @@ -1345,6 +1343,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout) if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result == CURLE_OK) { + XML_Parser parser = XML_ParserCreate(NULL); + enum XML_Status result; ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1363,6 +1363,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) XML_GetErrorCode(parser))); lock->timeout = -1; } + XML_ParserFree(parser); } } else { fprintf(stderr, "Unable to start LOCK request\n"); @@ -1525,8 +1526,6 @@ static void remote_ls(const char *path, int flags, struct buffer out_buffer; char *in_data; char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; struct remote_ls_ctx ls; @@ -1569,6 +1568,8 @@ static void remote_ls(const char *path, int flags, if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result == CURLE_OK) { + XML_Parser parser = XML_ParserCreate(NULL); + enum XML_Status result; ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1587,6 +1588,7 @@ static void remote_ls(const char *path, int flags, XML_ErrorString( XML_GetErrorCode(parser))); } + XML_ParserFree(parser); } } else { fprintf(stderr, "Unable to start PROPFIND request\n"); @@ -1620,8 +1622,6 @@ static int locking_available(void) struct buffer out_buffer; char *in_data; char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; int lock_flags = 0; @@ -1658,6 +1658,8 @@ static int locking_available(void) if (start_active_slot(slot)) { run_active_slot(slot); if (results.curl_result == CURLE_OK) { + XML_Parser parser = XML_ParserCreate(NULL); + enum XML_Status result; ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1676,6 +1678,7 @@ static int locking_available(void) XML_GetErrorCode(parser))); lock_flags = 0; } + XML_ParserFree(parser); } } else { fprintf(stderr, "Unable to start PROPFIND request\n"); @@ -4,31 +4,31 @@ int data_received; int active_requests = 0; #ifdef USE_CURL_MULTI -int max_requests = -1; -CURLM *curlm; +static int max_requests = -1; +static CURLM *curlm; #endif #ifndef NO_CURL_EASY_DUPHANDLE -CURL *curl_default; +static CURL *curl_default; #endif char curl_errorstr[CURL_ERROR_SIZE]; -int curl_ssl_verify = -1; -char *ssl_cert = NULL; +static int curl_ssl_verify = -1; +static char *ssl_cert = NULL; #if LIBCURL_VERSION_NUM >= 0x070902 -char *ssl_key = NULL; +static char *ssl_key = NULL; #endif #if LIBCURL_VERSION_NUM >= 0x070908 -char *ssl_capath = NULL; +static char *ssl_capath = NULL; #endif -char *ssl_cainfo = NULL; -long curl_low_speed_limit = -1; -long curl_low_speed_time = -1; -int curl_ftp_no_epsv = 0; -char *curl_http_proxy = NULL; +static char *ssl_cainfo = NULL; +static long curl_low_speed_limit = -1; +static long curl_low_speed_time = -1; +static int curl_ftp_no_epsv = 0; +static char *curl_http_proxy = NULL; -struct curl_slist *pragma_header; +static struct curl_slist *pragma_header; -struct active_request_slot *active_queue_head = NULL; +static struct active_request_slot *active_queue_head = NULL; size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, struct buffer *buffer) @@ -80,24 +80,6 @@ extern void http_cleanup(void); extern int data_received; extern int active_requests; -#ifndef NO_CURL_EASY_DUPHANDLE -extern CURL *curl_default; -#endif extern char curl_errorstr[CURL_ERROR_SIZE]; -extern int curl_ssl_verify; -extern char *ssl_cert; -#if LIBCURL_VERSION_NUM >= 0x070902 -extern char *ssl_key; -#endif -#if LIBCURL_VERSION_NUM >= 0x070908 -extern char *ssl_capath; -#endif -extern char *ssl_cainfo; -extern long curl_low_speed_limit; -extern long curl_low_speed_time; - -extern struct curl_slist *pragma_header; -extern struct curl_slist *no_range_header; - #endif /* HTTP_H */ @@ -182,14 +182,15 @@ static const char *env_hint = "Omit --global to set the identity only in this repository.\n" "\n"; -static const char *fmt_ident_1(const char *name, const char *email, - const char *date_str, int flag) +const char *fmt_ident(const char *name, const char *email, + const char *date_str, int flag) { static char buffer[1000]; char date[50]; int i; - int error_on_no_name = !!(flag & 01); - int name_addr_only = !!(flag & 02); + int error_on_no_name = (flag & IDENT_ERROR_ON_NO_NAME); + int warn_on_no_name = (flag & IDENT_WARN_ON_NO_NAME); + int name_addr_only = (flag & IDENT_NO_DATE); setup_ident(); if (!name) @@ -200,12 +201,12 @@ static const char *fmt_ident_1(const char *name, const char *email, if (!*name) { struct passwd *pw; - if (0 <= error_on_no_name && + if ((warn_on_no_name || error_on_no_name) && name == git_default_name && env_hint) { fprintf(stderr, env_hint, au_env, co_env); env_hint = NULL; /* warn only once, for "git-var -l" */ } - if (0 < error_on_no_name) + if (error_on_no_name) die("empty ident %s <%s> not allowed", name, email); pw = getpwuid(getuid()); if (!pw) @@ -234,30 +235,23 @@ static const char *fmt_ident_1(const char *name, const char *email, return buffer; } -const char *fmt_ident(const char *name, const char *email, - const char *date_str, int error_on_no_name) -{ - int flag = (error_on_no_name ? 01 : 0); - return fmt_ident_1(name, email, date_str, flag); -} - const char *fmt_name(const char *name, const char *email) { - return fmt_ident_1(name, email, NULL, 03); + return fmt_ident(name, email, NULL, IDENT_ERROR_ON_NO_NAME | IDENT_NO_DATE); } -const char *git_author_info(int error_on_no_name) +const char *git_author_info(int flag) { return fmt_ident(getenv("GIT_AUTHOR_NAME"), getenv("GIT_AUTHOR_EMAIL"), getenv("GIT_AUTHOR_DATE"), - error_on_no_name); + flag); } -const char *git_committer_info(int error_on_no_name) +const char *git_committer_info(int flag) { return fmt_ident(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"), getenv("GIT_COMMITTER_DATE"), - error_on_no_name); + flag); } diff --git a/merge-recursive.c b/merge-recursive.c index 9a1e2f269d..2a58dad3f4 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1046,14 +1046,16 @@ static struct merge_file_info merge_file(struct diff_filespec *o, free(result_buf.ptr); result.clean = (merge_status == 0); - } else { - if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) - die("cannot merge modes?"); - + } else if (S_ISGITLINK(a->mode)) { + result.clean = 0; + hashcpy(result.sha, a->sha1); + } else if (S_ISLNK(a->mode)) { hashcpy(result.sha, a->sha1); if (!sha_eq(a->sha1, b->sha1)) result.clean = 0; + } else { + die("unsupported object type in the tree"); } } @@ -5,6 +5,8 @@ * something different on Windows, for example. */ +static int spawned_pager; + static void run_pager(const char *pager) { /* @@ -41,7 +43,7 @@ void setup_pager(void) else if (!*pager || !strcmp(pager, "cat")) return; - pager_in_use = 1; /* means we are emitting to terminal */ + spawned_pager = 1; /* means we are emitting to terminal */ if (pipe(fd) < 0) return; @@ -70,3 +72,14 @@ void setup_pager(void) die("unable to execute pager '%s'", pager); exit(255); } + +int pager_in_use(void) +{ + const char *env; + + if (spawned_pager) + return 1; + + env = getenv("GIT_PAGER_IN_USE"); + return env ? git_config_bool("GIT_PAGER_IN_USE", env) : 0; +} diff --git a/perl/Makefile.PL b/perl/Makefile.PL index 6aecd897f8..320253eb8e 100644 --- a/perl/Makefile.PL +++ b/perl/Makefile.PL @@ -17,9 +17,6 @@ if ($@ || $Error::VERSION < 0.15009) { $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm'; } -my %extra; -$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR}; - # redirect stdout, otherwise the message "Writing perl.mak for Git" # disrupts the output for the target 'instlibdir' open STDOUT, ">&STDERR"; @@ -29,6 +26,5 @@ WriteMakefile( VERSION_FROM => 'Git.pm', PM => \%pm, MAKEFILE => 'perl.mak', - INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3', - %extra + INSTALLSITEMAN3DIR => '$(SITEPREFIX)/share/man/man3' ); @@ -1094,7 +1094,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1, adjust_shared_perm(log_file); msglen = msg ? strlen(msg) : 0; - committer = git_committer_info(-1); + committer = git_committer_info(0); maxlen = strlen(committer) + msglen + 100; logrec = xmalloc(maxlen); len = sprintf(logrec, "%s %s %s\n", @@ -388,7 +388,6 @@ int check_repository_format(void) const char *setup_git_directory(void) { const char *retval = setup_git_directory_gently(NULL); - check_repository_format(); /* If the work tree is not the default one, recompute prefix */ if (inside_work_tree < 0) { diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 8d4a447213..9ee35e7901 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -82,3 +82,29 @@ stop_httpd () { test -z "$SVN_HTTPD_PORT" && return "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop } + +convert_to_rev_db () { + perl -w -- - "$@" <<\EOF +use strict; +@ARGV == 2 or die "Usage: convert_to_rev_db <input> <output>"; +open my $wr, '+>', $ARGV[1] or die "$!: couldn't open: $ARGV[1]"; +open my $rd, '<', $ARGV[0] or die "$!: couldn't open: $ARGV[0]"; +my $size = (stat($rd))[7]; +($size % 24) == 0 or die "Inconsistent size: $size"; +while (sysread($rd, my $buf, 24) == 24) { + my ($r, $c) = unpack('NH40', $buf); + my $offset = $r * 41; + seek $wr, 0, 2 or die $!; + my $pos = tell $wr; + if ($pos < $offset) { + for (1 .. (($offset - $pos) / 41)) { + print $wr (('0' x 40),"\n") or die $!; + } + } + seek $wr, $offset, 0 or die $!; + print $wr $c,"\n" or die $!; +} +close $wr or die $!; +close $rd or die $!; +EOF +} diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 79fdff3f3a..6adf9d11d0 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -117,4 +117,13 @@ EOF git diff -b > out test_expect_success 'another test, with -b' 'git diff expect out' + +test_expect_success 'check mixed spaces and tabs in indent' ' + + # This is indented with SP HT SP. + echo " foo();" > x && + git diff --check | grep "space before tab" + +' + test_done diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh new file mode 100755 index 0000000000..67e080bdbe --- /dev/null +++ b/t/t4019-diff-wserror.sh @@ -0,0 +1,123 @@ +#!/bin/sh + +test_description='diff whitespace error detection' + +. ./test-lib.sh + +test_expect_success setup ' + + git config diff.color.whitespace "blue reverse" && + >F && + git add F && + echo " Eight SP indent" >>F && + echo " HT and SP indent" >>F && + echo "With trailing SP " >>F && + echo "No problem" >>F + +' + +blue_grep='7;34m' ;# ESC [ 7 ; 3 4 m + +test_expect_success default ' + + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With error >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'without -trail' ' + + git config core.whitespace -trail + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'without -trail (attribute)' ' + + git config --unset core.whitespace + echo "F whitespace=-trail" >.gitattributes + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT error >/dev/null && + grep With normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'without -space' ' + + rm -f .gitattributes + git config core.whitespace -space + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT normal >/dev/null && + grep With error >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'without -space (attribute)' ' + + git config --unset core.whitespace + echo "F whitespace=-space" >.gitattributes + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight normal >/dev/null && + grep HT normal >/dev/null && + grep With error >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'with indent-non-tab only' ' + + rm -f .gitattributes + git config core.whitespace indent,-trailing,-space + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight error >/dev/null && + grep HT normal >/dev/null && + grep With normal >/dev/null && + grep No normal >/dev/null + +' + +test_expect_success 'with indent-non-tab only (attribute)' ' + + git config --unset core.whitespace + echo "F whitespace=indent,-trailing,-space" >.gitattributes + git diff --color >output + grep "$blue_grep" output >error + grep -v "$blue_grep" output >normal + + grep Eight error >/dev/null && + grep HT normal >/dev/null && + grep With normal >/dev/null && + grep No normal >/dev/null + +' + +test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh new file mode 100755 index 0000000000..85f3da2b98 --- /dev/null +++ b/t/t4124-apply-ws-rule.sh @@ -0,0 +1,151 @@ +#!/bin/sh + +test_description='core.whitespace rules and git-apply' + +. ./test-lib.sh + +prepare_test_file () { + + # A line that has character X is touched iff RULE is in effect: + # X RULE + # ! trailing-space + # @ space-before-tab + # # indent-with-non-tab + sed -e "s/_/ /g" -e "s/>/ /" <<-\EOF + An_SP in an ordinary line>and a HT. + >A HT. + _>A SP and a HT (@). + _>_A SP, a HT and a SP (@). + _______Seven SP. + ________Eight SP (#). + _______>Seven SP and a HT (@). + ________>Eight SP and a HT (@#). + _______>_Seven SP, a HT and a SP (@). + ________>_Eight SP, a HT and a SP (@#). + _______________Fifteen SP (#). + _______________>Fifteen SP and a HT (@#). + ________________Sixteen SP (#). + ________________>Sixteen SP and a HT (@#). + _____a__Five SP, a non WS, two SP. + A line with a (!) trailing SP_ + A line with a (!) trailing HT> + EOF +} + +apply_patch () { + >target && + sed -e "s|\([ab]\)/file|\1/target|" <patch | + git apply "$@" +} + +test_fix () { + + # fix should not barf + apply_patch --whitespace=fix || return 1 + + # find touched lines + diff file target | sed -n -e "s/^> //p" >fixed + + # the changed lines are all expeced to change + fixed_cnt=$(wc -l <fixed) + case "$1" in + '') expect_cnt=$fixed_cnt ;; + ?*) expect_cnt=$(grep "[$1]" <fixed | wc -l) ;; + esac + test $fixed_cnt -eq $expect_cnt || return 1 + + # and we are not missing anything + case "$1" in + '') expect_cnt=0 ;; + ?*) expect_cnt=$(grep "[$1]" <file | wc -l) ;; + esac + test $fixed_cnt -eq $expect_cnt || return 1 + + # Get the patch actually applied + git diff-files -p target >fixed-patch + test -s fixed-patch && return 0 + + # Make sure it is complaint-free + >target + git apply --whitespace=error-all <fixed-patch + +} + +test_expect_success setup ' + + >file && + git add file && + prepare_test_file >file && + git diff-files -p >patch && + >target && + git add target + +' + +test_expect_success 'whitespace=nowarn, default rule' ' + + apply_patch --whitespace=nowarn && + diff file target + +' + +test_expect_success 'whitespace=warn, default rule' ' + + apply_patch --whitespace=warn && + diff file target + +' + +test_expect_success 'whitespace=error-all, default rule' ' + + apply_patch --whitespace=error-all && return 1 + test -s target && return 1 + : happy + +' + +test_expect_success 'whitespace=error-all, no rule' ' + + git config core.whitespace -trailing,-space-before,-indent && + apply_patch --whitespace=error-all && + diff file target + +' + +test_expect_success 'whitespace=error-all, no rule (attribute)' ' + + git config --unset core.whitespace && + echo "target -whitespace" >.gitattributes && + apply_patch --whitespace=error-all && + diff file target + +' + +for t in - '' +do + case "$t" in '') tt='!' ;; *) tt= ;; esac + for s in - '' + do + case "$s" in '') ts='@' ;; *) ts= ;; esac + for i in - '' + do + case "$i" in '') ti='#' ;; *) ti= ;; esac + rule=${t}trailing,${s}space,${i}indent + + rm -f .gitattributes + test_expect_success "rule=$rule" ' + git config core.whitespace "$rule" && + test_fix "$tt$ts$ti" + ' + + test_expect_success "rule=$rule (attributes)" ' + git config --unset core.whitespace && + echo "target whitespace=$rule" >.gitattributes && + test_fix "$tt$ts$ti" + ' + + done + done +done + +test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index c7130c4dcc..09d56e0839 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -640,6 +640,46 @@ test_expect_success 'creating a signed tag with -m message should succeed' ' git diff expect actual ' +get_tag_header u-signed-tag $commit commit $time >expect +echo 'Another message' >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success 'sign with a given key id' ' + + git tag -u committer@example.com -m "Another message" u-signed-tag && + get_tag_msg u-signed-tag >actual && + git diff expect actual + +' + +test_expect_success 'sign with an unknown id (1)' ' + + ! git tag -u author@example.com -m "Another message" o-signed-tag + +' + +test_expect_success 'sign with an unknown id (2)' ' + + ! git tag -u DEADBEEF -m "Another message" o-signed-tag + +' + +cat >fakeeditor <<'EOF' +#!/bin/sh +test -n "$1" && exec >"$1" +echo A signed tag message +echo from a fake editor. +EOF +chmod +x fakeeditor + +get_tag_header implied-sign $commit commit $time >expect +./fakeeditor >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success '-u implies signed tag' ' + GIT_EDITOR=./fakeeditor git-tag -u CDDE430D implied-sign && + get_tag_msg implied-sign >actual && + git diff expect actual +' + cat >sigmsgfile <<EOF Another signed tag message in a file. @@ -667,13 +707,6 @@ test_expect_success 'creating a signed tag with -F - should succeed' ' git diff expect actual ' -cat >fakeeditor <<'EOF' -#!/bin/sh -test -n "$1" && exec >"$1" -echo A signed tag message -echo from a fake editor. -EOF -chmod +x fakeeditor get_tag_header implied-annotate $commit commit $time >expect ./fakeeditor >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 55558aba8b..73d8a00e2c 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -20,6 +20,8 @@ Test switching across them. . ./test-lib.sh +test_tick + fill () { for i do @@ -30,9 +32,10 @@ fill () { test_expect_success setup ' + fill x y z > same && fill 1 2 3 4 5 6 7 8 >one && fill a b c d e >two && - git add one two && + git add same one two && git commit -m "Initial A one, A two" && git checkout -b renamer && @@ -74,16 +77,44 @@ test_expect_success "checkout with dirty tree without -m" ' ' +test_expect_success "checkout with unrelated dirty tree without -m" ' + + git checkout -f master && + fill 0 1 2 3 4 5 6 7 8 >same && + cp same kept + git checkout side >messages && + git diff same kept + (cat > messages.expect <<EOF +M same +EOF +) && + touch messages.expect && + git diff messages.expect messages +' + test_expect_success "checkout -m with dirty tree" ' git checkout -f master && git clean -f && fill 0 1 2 3 4 5 6 7 8 >one && - git checkout -m side && + git checkout -m side > messages && test "$(git symbolic-ref HEAD)" = "refs/heads/side" && + (cat >expect.messages <<EOF +Merging side with local +Merging: +ab76817 Side M one, D two, A three +virtual local +found 1 common ancestor(s): +7329388 Initial A one, A two +Auto-merged one +M one +EOF +) && + git diff expect.messages messages && + fill "M one" "A three" "D two" >expect.master && git diff --name-status master >current.master && diff expect.master current.master && @@ -145,7 +176,16 @@ test_expect_success 'checkout -m with merge conflict' ' test_expect_success 'checkout to detach HEAD' ' git checkout -f renamer && git clean -f && - git checkout renamer^ && + git checkout renamer^ 2>messages && + (cat >messages.expect <<EOF +Note: moving to "renamer^" which isn'"'"'t a local branch +If you want to create a new branch from this checkout, you may do so +(now or later) by using -b with the checkout command again. Example: + git checkout -b <new_branch_name> +HEAD is now at 7329388... Initial A one, A two +EOF +) && + git diff messages.expect messages && H=$(git rev-parse --verify HEAD) && M=$(git show-ref -s --verify refs/heads/master) && test "z$H" = "z$M" && diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 19c4b2c556..05aa97d6f3 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -4,7 +4,7 @@ # # FIXME: Test the various index usages, -i and -o, test reflog, -# signoff, hooks +# signoff test_description='git-commit' . ./test-lib.sh diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh new file mode 100755 index 0000000000..d787cac2f7 --- /dev/null +++ b/t/t7503-pre-commit-hook.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +test_description='pre-commit hook' + +. ./test-lib.sh + +test_expect_success 'with no hook' ' + + echo "foo" > file && + git add file && + git commit -m "first" + +' + +test_expect_success '--no-verify with no hook' ' + + echo "bar" > file && + git add file && + git commit --no-verify -m "bar" + +' + +# now install hook that always succeeds +HOOKDIR="$(git rev-parse --git-dir)/hooks" +HOOK="$HOOKDIR/pre-commit" +mkdir -p "$HOOKDIR" +cat > "$HOOK" <<EOF +#!/bin/sh +exit 0 +EOF +chmod +x "$HOOK" + +test_expect_success 'with succeeding hook' ' + + echo "more" >> file && + git add file && + git commit -m "more" + +' + +test_expect_success '--no-verify with succeeding hook' ' + + echo "even more" >> file && + git add file && + git commit --no-verify -m "even more" + +' + +# now a hook that fails +cat > "$HOOK" <<EOF +#!/bin/sh +exit 1 +EOF + +test_expect_failure 'with failing hook' ' + + echo "another" >> file && + git add file && + git commit -m "another" + +' + +test_expect_success '--no-verify with failing hook' ' + + echo "stuff" >> file && + git add file && + git commit --no-verify -m "stuff" + +' + +chmod -x "$HOOK" +test_expect_success 'with non-executable hook' ' + + echo "content" >> file && + git add file && + git commit -m "content" + +' + +test_expect_success '--no-verify with non-executable hook' ' + + echo "more content" >> file && + git add file && + git commit --no-verify -m "more content" + +' + +test_done diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh new file mode 100755 index 0000000000..751b11300b --- /dev/null +++ b/t/t7504-commit-msg-hook.sh @@ -0,0 +1,220 @@ +#!/bin/sh + +test_description='commit-msg hook' + +. ./test-lib.sh + +test_expect_success 'with no hook' ' + + echo "foo" > file && + git add file && + git commit -m "first" + +' + +# set up fake editor for interactive editing +cat > fake-editor <<'EOF' +#!/bin/sh +cp FAKE_MSG "$1" +exit 0 +EOF +chmod +x fake-editor +FAKE_EDITOR="$(pwd)/fake-editor" +export FAKE_EDITOR + +test_expect_success 'with no hook (editor)' ' + + echo "more foo" >> file && + git add file && + echo "more foo" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit + +' + +test_expect_success '--no-verify with no hook' ' + + echo "bar" > file && + git add file && + git commit --no-verify -m "bar" + +' + +test_expect_success '--no-verify with no hook (editor)' ' + + echo "more bar" > file && + git add file && + echo "more bar" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify + +' + +# now install hook that always succeeds +HOOKDIR="$(git rev-parse --git-dir)/hooks" +HOOK="$HOOKDIR/commit-msg" +mkdir -p "$HOOKDIR" +cat > "$HOOK" <<EOF +#!/bin/sh +exit 0 +EOF +chmod +x "$HOOK" + +test_expect_success 'with succeeding hook' ' + + echo "more" >> file && + git add file && + git commit -m "more" + +' + +test_expect_success 'with succeeding hook (editor)' ' + + echo "more more" >> file && + git add file && + echo "more more" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit + +' + +test_expect_success '--no-verify with succeeding hook' ' + + echo "even more" >> file && + git add file && + git commit --no-verify -m "even more" + +' + +test_expect_success '--no-verify with succeeding hook (editor)' ' + + echo "even more more" >> file && + git add file && + echo "even more more" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify + +' + +# now a hook that fails +cat > "$HOOK" <<EOF +#!/bin/sh +exit 1 +EOF + +test_expect_failure 'with failing hook' ' + + echo "another" >> file && + git add file && + git commit -m "another" + +' + +test_expect_failure 'with failing hook (editor)' ' + + echo "more another" >> file && + git add file && + echo "more another" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit + +' + +test_expect_success '--no-verify with failing hook' ' + + echo "stuff" >> file && + git add file && + git commit --no-verify -m "stuff" + +' + +test_expect_success '--no-verify with failing hook (editor)' ' + + echo "more stuff" >> file && + git add file && + echo "more stuff" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify + +' + +chmod -x "$HOOK" +test_expect_success 'with non-executable hook' ' + + echo "content" >> file && + git add file && + git commit -m "content" + +' + +test_expect_success 'with non-executable hook (editor)' ' + + echo "content again" >> file && + git add file && + echo "content again" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit -m "content again" + +' + +test_expect_success '--no-verify with non-executable hook' ' + + echo "more content" >> file && + git add file && + git commit --no-verify -m "more content" + +' + +test_expect_success '--no-verify with non-executable hook (editor)' ' + + echo "even more content" >> file && + git add file && + echo "even more content" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify + +' + +# now a hook that edits the commit message +cat > "$HOOK" <<'EOF' +#!/bin/sh +echo "new message" > "$1" +exit 0 +EOF +chmod +x "$HOOK" + +commit_msg_is () { + test "`git log --pretty=format:%s%b -1`" = "$1" +} + +test_expect_success 'hook edits commit message' ' + + echo "additional" >> file && + git add file && + git commit -m "additional" && + commit_msg_is "new message" + +' + +test_expect_success 'hook edits commit message (editor)' ' + + echo "additional content" >> file && + git add file && + echo "additional content" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit && + commit_msg_is "new message" + +' + +test_expect_success "hook doesn't edit commit message" ' + + echo "plus" >> file && + git add file && + git commit --no-verify -m "plus" && + commit_msg_is "plus" + +' + +test_expect_success "hook doesn't edit commit message (editor)" ' + + echo "more plus" >> file && + git add file && + echo "more plus" > FAKE_MSG && + GIT_EDITOR="$FAKE_EDITOR" git commit --no-verify && + commit_msg_is "more plus" + +' + +test_done diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 67fdf7023f..0a41d52c7a 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -97,15 +97,19 @@ test_expect_success 'migrate --minimize on old inited layout' " grep '^:refs/remotes/git-svn' fetch.out " -test_expect_success ".rev_db auto-converted to .rev_db.UUID" " +test_expect_success ".rev_db auto-converted to .rev_map.UUID" " git-svn fetch -i trunk && - expect=$GIT_DIR/svn/trunk/.rev_db.* && + test -z \"\$(ls $GIT_DIR/svn/trunk/.rev_db.* 2>/dev/null)\" && + expect=\"\$(ls $GIT_DIR/svn/trunk/.rev_map.*)\" && test -n \"\$expect\" && - mv \$expect $GIT_DIR/svn/trunk/.rev_db && + rev_db=\$(echo \$expect | sed -e 's,_map,_db,') && + convert_to_rev_db \$expect \$rev_db && + rm -f \$expect && + test -f \$rev_db && git-svn fetch -i trunk && - test -L $GIT_DIR/svn/trunk/.rev_db && - test -f \$expect && - cmp \$expect $GIT_DIR/svn/trunk/.rev_db + test -z \"\$(ls $GIT_DIR/svn/trunk/.rev_db.* 2>/dev/null)\" && + test ! -e $GIT_DIR/svn/trunk/.rev_db && + test -f \$expect " test_done diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index 439bd93c88..cc61911593 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -5,6 +5,8 @@ test_description='git-svn info' . ./lib-git-svn.sh +say 'skipping svn-info test (has a race undiagnosed yet)' +test_done ptouch() { perl -w -e ' diff --git a/tree-diff.c b/tree-diff.c index aa0a100295..e1e2e6c6ce 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -326,6 +326,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co die("unable to set up diff options to follow renames"); diff_tree(t1, t2, base, &diff_opts); diffcore_std(&diff_opts); + diff_tree_release_paths(&diff_opts); /* Go through the new set of filepairing, and see if we find a more interesting one */ for (i = 0; i < q->nr; i++) { @@ -342,6 +343,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co choice = p; /* Update the path we use from now on.. */ + diff_tree_release_paths(opt); opt->paths[0] = xstrdup(p->one->path); diff_tree_setup_paths(opt->paths, opt); break; @@ -21,7 +21,7 @@ static void list_vars(void) { struct git_var *ptr; for(ptr = git_vars; ptr->read; ptr++) { - printf("%s=%s\n", ptr->name, ptr->read(0)); + printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME)); } } @@ -32,7 +32,7 @@ static const char *read_var(const char *var) val = NULL; for(ptr = git_vars; ptr->read; ptr++) { if (strcmp(var, ptr->name) == 0) { - val = ptr->read(1); + val = ptr->read(IDENT_ERROR_ON_NO_NAME); break; } } @@ -0,0 +1,96 @@ +/* + * Whitespace rules + * + * Copyright (c) 2007 Junio C Hamano + */ + +#include "cache.h" +#include "attr.h" + +static struct whitespace_rule { + const char *rule_name; + unsigned rule_bits; +} whitespace_rule_names[] = { + { "trailing-space", WS_TRAILING_SPACE }, + { "space-before-tab", WS_SPACE_BEFORE_TAB }, + { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB }, +}; + +unsigned parse_whitespace_rule(const char *string) +{ + unsigned rule = WS_DEFAULT_RULE; + + while (string) { + int i; + size_t len; + const char *ep; + int negated = 0; + + string = string + strspn(string, ", \t\n\r"); + ep = strchr(string, ','); + if (!ep) + len = strlen(string); + else + len = ep - string; + + if (*string == '-') { + negated = 1; + string++; + len--; + } + if (!len) + break; + for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) { + if (strncmp(whitespace_rule_names[i].rule_name, + string, len)) + continue; + if (negated) + rule &= ~whitespace_rule_names[i].rule_bits; + else + rule |= whitespace_rule_names[i].rule_bits; + break; + } + string = ep; + } + return rule; +} + +static void setup_whitespace_attr_check(struct git_attr_check *check) +{ + static struct git_attr *attr_whitespace; + + if (!attr_whitespace) + attr_whitespace = git_attr("whitespace", 10); + check[0].attr = attr_whitespace; +} + +unsigned whitespace_rule(const char *pathname) +{ + struct git_attr_check attr_whitespace_rule; + + setup_whitespace_attr_check(&attr_whitespace_rule); + if (!git_checkattr(pathname, 1, &attr_whitespace_rule)) { + const char *value; + + value = attr_whitespace_rule.value; + if (ATTR_TRUE(value)) { + /* true (whitespace) */ + unsigned all_rule = 0; + int i; + for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) + all_rule |= whitespace_rule_names[i].rule_bits; + return all_rule; + } else if (ATTR_FALSE(value)) { + /* false (-whitespace) */ + return 0; + } else if (ATTR_UNSET(value)) { + /* reset to default (!whitespace) */ + return whitespace_rule_cfg; + } else { + /* string */ + return parse_whitespace_rule(value); + } + } else { + return whitespace_rule_cfg; + } +} |