diff options
62 files changed, 1074 insertions, 186 deletions
@@ -36,7 +36,7 @@ Lars Doelle <lars.doelle@on-line ! de> Lars Doelle <lars.doelle@on-line.de> Li Hong <leehong@pku.edu.cn> Lukas Sandström <lukass@etek.chalmers.se> -Martin Langhoff <martin@catalyst.net.nz> +Martin Langhoff <martin@laptop.org> Michael Coleman <tutufan@gmail.com> Michael J Gruber <git@drmicha.warpmail.net> <michaeljgruber+gmane@fastmail.fm> Michael W. Olson <mwolson@gnu.org> diff --git a/Documentation/RelNotes/1.7.4.txt b/Documentation/RelNotes/1.7.4.txt new file mode 100644 index 0000000000..05e8a43a3b --- /dev/null +++ b/Documentation/RelNotes/1.7.4.txt @@ -0,0 +1,54 @@ +Git v1.7.4 Release Notes (draft) +================================ + +Updates since v1.7.3 +-------------------- + + * The option parsers of various commands that create new branch (or + rename existing ones to a new name) were too loose and users were + allowed to call a branch with a name that begins with a dash by + creative abuse of their command line options, which only lead to + burn themselves. The name of a branch cannot begin with a dash + now. + + * System-wide fallback default attributes can be stored in + /etc/gitattributes; core.attributesfile configuration variable can + be used to customize the path to this file. + + * "git diff" and "git grep" learned how functions and subroutines + in Fortran look like. + + * "git log -G<pattern>" limits the output to commits whose change has + added or deleted lines that match the given pattern. + + * "git read-tree" with no argument as a way to empty the index is + deprecated; we might want to remove it in the future. Users can + use the new --empty option to be more explicit instead. + + * "git merge --log" used to limit the resulting merge log to 20 + entries; this is now customizable by giving e.g. "--log=47". + + * you can extend "git shell", which is often used on boxes that allow + git-only login over ssh as login shell, with custom set of + commands. + +Also contains various documentation updates. + + +Fixes since v1.7.3 +------------------ + +All of the fixes in v1.7.3.X maintenance series are included in this +release, unless otherwise noted. + + * "git log --author=me --author=her" did not find commits written by + me or by her; instead it looked for commits written by me and by + her, which is impossible. + + +--- +exec >/var/tmp/1 +O=v1.7.3 +O=v1.7.3.1-42-g34289ec +echo O=$(git describe master) +git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/config.txt b/Documentation/config.txt index d82c0da2cf..e6d74e6ade 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -459,6 +459,12 @@ core.askpass:: prompt. The external program shall be given a suitable prompt as command line argument and write the password on its STDOUT. +core.attributesfile:: + In addition to '.gitattributes' (per-directory) and + '.git/info/attributes', git looks into this file for attributes + (see linkgit:gitattributes[5]). Path expansions are made the same + way as for `core.excludesfile`. + core.editor:: Commands such as `commit` and `tag` that lets you edit messages by launching an editor uses the value of this diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 4656a97e60..f77a0f8749 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -284,8 +284,12 @@ ifndef::git-format-patch[] appearing in diff output; see the 'pickaxe' entry in linkgit:gitdiffcore[7] for more details. +-G<regex>:: + Look for differences whose added or removed line matches + the given <regex>. + --pickaxe-all:: - When `-S` finds a change, show all the changes in that + When `-S` or `-G` finds a change, show all the changes in that changeset, not just the files that contain the change in <string>. diff --git a/Documentation/git-archimport.txt b/Documentation/git-archimport.txt index 4f358c8d6c..2411ce5bfe 100644 --- a/Documentation/git-archimport.txt +++ b/Documentation/git-archimport.txt @@ -109,7 +109,7 @@ OPTIONS Author ------ -Written by Martin Langhoff <martin@catalyst.net.nz>. +Written by Martin Langhoff <martin@laptop.org>. Documentation -------------- diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index b2696efae9..d25661eb21 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -114,11 +114,11 @@ $ git cherry cvshead myhead | sed -n 's/^+ //p' | xargs -l1 git cvsexportcommit Author ------ -Written by Martin Langhoff <martin@catalyst.net.nz> and others. +Written by Martin Langhoff <martin@laptop.org> and others. Documentation -------------- -Documentation by Martin Langhoff <martin@catalyst.net.nz> and others. +Documentation by Martin Langhoff <martin@laptop.org> and others. GIT --- diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index f4472c61db..70cbb2cae7 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -399,13 +399,13 @@ This program is copyright The Open University UK - 2006. Authors: - Martyn Smith <martyn@catalyst.net.nz> -- Martin Langhoff <martin@catalyst.net.nz> +- Martin Langhoff <martin@laptop.org> with ideas and patches from participants of the git-list <git@vger.kernel.org>. Documentation -------------- -Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@catalyst.net.nz>, and Matthias Urlichs <smurf@smurf.noris.de>. +Documentation by Martyn Smith <martyn@catalyst.net.nz>, Martin Langhoff <martin@laptop.org>, and Matthias Urlichs <smurf@smurf.noris.de>. GIT --- diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 302f56b889..40dba8c0a9 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -9,8 +9,8 @@ git-fmt-merge-msg - Produce a merge commit message SYNOPSIS -------- [verse] -'git fmt-merge-msg' [-m <message>] [--log | --no-log] <$GIT_DIR/FETCH_HEAD -'git fmt-merge-msg' [-m <message>] [--log | --no-log] -F <file> +'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] <$GIT_DIR/FETCH_HEAD +'git fmt-merge-msg' [-m <message>] [--log[=<n>] | --no-log] -F <file> DESCRIPTION ----------- @@ -24,10 +24,12 @@ automatically invoking 'git merge'. OPTIONS ------- ---log:: +--log[=<n>]:: In addition to branch names, populate the log message with one-line descriptions from the actual commits that are being - merged. + merged. At most <n> commits from each merge parent will be + used (20 if <n> is omitted). This overrides the `merge.log` + configuration variable. --no-log:: Do not list one-line descriptions from the actual commits being @@ -52,8 +54,10 @@ CONFIGURATION ------------- merge.log:: - Whether to include summaries of merged commits in newly - merge commit messages. False by default. + In addition to branch names, populate the log message with at + most the specified number of one-line descriptions from the + actual commits that are being merged. Defaults to false, and + true is a synoym for 20. merge.summary:: Synonym to `merge.log`; this is deprecated and will be removed in diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 4b3f5ba535..df77474c29 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -74,7 +74,7 @@ OPTIONS include::diff-options.txt[] -<n>:: - Limits the number of patches to prepare. + Prepare patches from the topmost <n> commits. -o <dir>:: --output-directory <dir>:: diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 2e78da448f..e88e9c2d55 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -11,7 +11,7 @@ SYNOPSIS 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] [--no-sparse-checkout] - <tree-ish1> [<tree-ish2> [<tree-ish3>]] + (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -114,6 +114,10 @@ OPTIONS Disable sparse checkout support even if `core.sparseCheckout` is true. +--empty:: + Instead of reading tree object(s) into the index, just empty + it. + <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 0f3ad811cf..6403126a02 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -3,24 +3,30 @@ git-shell(1) NAME ---- -git-shell - Restricted login shell for GIT-only SSH access +git-shell - Restricted login shell for Git-only SSH access SYNOPSIS -------- -'$(git --exec-path)/git-shell' -c <command> <argument> +'git shell' [-c <command> <argument>] DESCRIPTION ----------- -This is meant to be used as a login shell for SSH accounts you want -to restrict to GIT pull/push access only. It permits execution only -of server-side GIT commands implementing the pull/push functionality. -The commands can be executed only by the '-c' option; the shell is not -interactive. - -Currently, only four commands are permitted to be called, 'git-receive-pack' -'git-upload-pack' and 'git-upload-archive' with a single required argument, or -'cvs server' (to invoke 'git-cvsserver'). + +A login shell for SSH accounts to provide restricted Git access. When +'-c' is given, the program executes <command> non-interactively; +<command> can be one of 'git receive-pack', 'git upload-pack', 'git +upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell +is started in interactive mode when no arguments are given; in this +case, COMMAND_DIR must exist, and any of the executables in it can be +invoked. + +'cvs server' is a special command which executes git-cvsserver. + +COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have +read and execute permissions to the directory in order to execute the +programs in it. The programs are executed with a cwd of $HOME, and +<argument> is parsed as a command-line string. Author ------ diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index e5a27d875e..c80ca5da43 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -62,14 +62,21 @@ consults `$GIT_DIR/info/attributes` file (which has the highest precedence), `.gitattributes` file in the same directory as the path in question, and its parent directories up to the toplevel of the work tree (the further the directory that contains `.gitattributes` -is from the path in question, the lower its precedence). +is from the path in question, the lower its precedence). Finally +global and system-wide files are considered (they have the lowest +precedence). If you wish to affect only a single repository (i.e., to assign -attributes to files that are particular to one user's workflow), then +attributes to files that are particular to +one user's workflow for that repository), then attributes should be placed in the `$GIT_DIR/info/attributes` file. Attributes which should be version-controlled and distributed to other repositories (i.e., attributes of interest to all users) should go into -`.gitattributes` files. +`.gitattributes` files. Attributes that should affect all repositories +for a single user should be placed in a file specified by the +`core.attributesfile` configuration option (see linkgit:git-config[1]). +Attributes for all users on a system should be placed in the +`$(prefix)/etc/gitattributes` file. Sometimes you would need to override an setting of an attribute for a path to `unspecified` state. This can be done by listing @@ -477,6 +484,8 @@ patterns are available: - `csharp` suitable for source code in the C# language. +- `fortran` suitable for source code in the Fortran language. + - `html` suitable for HTML/XHTML documents. - `java` suitable for source code in the Java language. diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 5d91a7e5b3..6af29a4603 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -227,9 +227,9 @@ changes that touch a specified string, and is controlled by the commands. When diffcore-pickaxe is in use, it checks if there are -filepairs whose "result" side has the specified string and -whose "origin" side does not. Such a filepair represents "the -string appeared in this changeset". It also checks for the +filepairs whose "result" side and whose "origin" side have +different number of specified string. Such a filepair represents +"the string appeared in this changeset". It also checks for the opposite case that loses the specified string. When `\--pickaxe-all` is not in effect, diffcore-pickaxe leaves diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index b72f533970..92772e7c4e 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -7,8 +7,10 @@ merge.conflictstyle:: marker and the original text before the `=======` marker. merge.log:: - Whether to include summaries of merged commits in newly created - merge commit messages. False by default. + In addition to branch names, populate the log message with at + most the specified number of one-line descriptions from the + actual commits that are being merged. Defaults to false, and + true is a synoym for 20. merge.renameLimit:: The number of files to consider when performing rename detection diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 722d704ff2..e33e0f8e11 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -16,11 +16,11 @@ inspect and further tweak the merge result before committing. With --no-ff Generate a merge commit even if the merge resolved as a fast-forward. ---log:: +--log[=<n>]:: --no-log:: In addition to branch names, populate the log message with - one-line descriptions from the actual commits that are being - merged. + one-line descriptions from at most <n> actual commits that are being + merged. See also linkgit:git-fmt-merge-msg[1]. + With --no-log do not list one-line descriptions from the actual commits being merged. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 75b8b3cc69..d441d88d6f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.3.1 +DEF_VER=v1.7.3.GIT LF=' ' @@ -270,6 +270,7 @@ STRIP ?= strip # infodir # htmldir # ETC_GITCONFIG (but not sysconfdir) +# ETC_GITATTRIBUTES # can be specified as a relative path some/where/else; # this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. @@ -288,9 +289,11 @@ htmldir = share/doc/git-doc ifeq ($(prefix),/usr) sysconfdir = /etc ETC_GITCONFIG = $(sysconfdir)/gitconfig +ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes else sysconfdir = $(prefix)/etc ETC_GITCONFIG = etc/gitconfig +ETC_GITATTRIBUTES = etc/gitattributes endif lib = lib # DESTDIR= @@ -1523,6 +1526,7 @@ endif SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) +ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) @@ -1901,6 +1905,8 @@ builtin/init-db.s builtin/init-db.o: EXTRA_CPPFLAGS = \ config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"' +attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' + http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' ifdef NO_EXPAT @@ -1 +1 @@ -Documentation/RelNotes/1.7.3.1.txt
\ No newline at end of file +Documentation/RelNotes/1.7.4.txt
\ No newline at end of file @@ -108,10 +108,14 @@ const char *make_nonrelative_path(const char *path) if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX) die("Too long path: %.*s", 60, path); } else { + size_t len; + const char *fmt; const char *cwd = get_pwd_cwd(); if (!cwd) die_errno("Cannot determine the current working directory"); - if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX) + len = strlen(cwd); + fmt = (len > 0 && is_dir_sep(cwd[len-1])) ? "%s%s" : "%s/%s"; + if (snprintf(buf, PATH_MAX, fmt, cwd, path) >= PATH_MAX) die("Too long path: %.*s", 60, path); } return buf; @@ -1,5 +1,6 @@ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "exec_cmd.h" #include "attr.h" const char git_attr__true[] = "(builtin)true"; @@ -10,6 +11,8 @@ static const char git_attr__unknown[] = "(builtin)unknown"; #define ATTR__UNSET NULL #define ATTR__UNKNOWN git_attr__unknown +static const char *attributes_file; + /* * The basic design decision here is that we are not going to have * insanely large number of attributes. @@ -462,6 +465,32 @@ static void drop_attr_stack(void) } } +const char *git_etc_gitattributes(void) +{ + static const char *system_wide; + if (!system_wide) + system_wide = system_path(ETC_GITATTRIBUTES); + return system_wide; +} + +int git_attr_system(void) +{ + return !git_env_bool("GIT_ATTR_NOSYSTEM", 0); +} + +int git_attr_global(void) +{ + return !git_env_bool("GIT_ATTR_NOGLOBAL", 0); +} + +static int git_attr_config(const char *var, const char *value, void *dummy) +{ + if (!strcmp(var, "core.attributesfile")) + return git_config_pathname(&attributes_file, var, value); + + return 0; +} + static void bootstrap_attr_stack(void) { if (!attr_stack) { @@ -472,6 +501,25 @@ static void bootstrap_attr_stack(void) elem->prev = attr_stack; attr_stack = elem; + if (git_attr_system()) { + elem = read_attr_from_file(git_etc_gitattributes(), 1); + if (elem) { + elem->origin = NULL; + elem->prev = attr_stack; + attr_stack = elem; + } + } + + git_config(git_attr_config, NULL); + if (git_attr_global() && attributes_file) { + elem = read_attr_from_file(attributes_file, 1); + if (elem) { + elem->origin = NULL; + elem->prev = attr_stack; + attr_stack = elem; + } + } + if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { elem = read_attr(GITATTRIBUTES_FILE, 1); elem->origin = strdup(""); @@ -499,7 +547,9 @@ static void prepare_attr_stack(const char *path, int dirlen) /* * At the bottom of the attribute stack is the built-in - * set of attribute definitions. Then, contents from + * set of attribute definitions, followed by the contents + * of $(prefix)/etc/gitattributes and a file specified by + * core.attributesfile. Then, contents from * .gitattribute files from directories closer to the * root to the ones in deeper directories are pushed * to the stack. Finally, at the very top of the stack @@ -7,14 +7,15 @@ #include "commit.h" #include "notes.h" +#define DEFAULT_MERGE_LOG_LEN 20 + extern const char git_version_string[]; extern const char git_usage_string[]; extern const char git_more_info_string[]; extern void prune_packed_objects(int); -extern int fmt_merge_msg(int merge_summary, struct strbuf *in, - struct strbuf *out); -extern int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out); +extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + int merge_title, int shortlog_len); extern int commit_notes(struct notes_tree *t, const char *msg); struct notes_rewrite_cfg { diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index e7e12eea25..78c77742b6 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -7,21 +7,22 @@ #include "string-list.h" static const char * const fmt_merge_msg_usage[] = { - "git fmt-merge-msg [-m <message>] [--log|--no-log] [--file <file>]", + "git fmt-merge-msg [-m <message>] [--log[=<n>]|--no-log] [--file <file>]", NULL }; -static int merge_summary; +static int shortlog_len; static int fmt_merge_msg_config(const char *key, const char *value, void *cb) { - static int found_merge_log = 0; - if (!strcmp("merge.log", key)) { - found_merge_log = 1; - merge_summary = git_config_bool(key, value); + if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool && shortlog_len < 0) + return error("%s: negative length %s", key, value); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; } - if (!found_merge_log && !strcmp("merge.summary", key)) - merge_summary = git_config_bool(key, value); return 0; } @@ -255,9 +256,9 @@ static void do_fmt_merge_msg_title(struct strbuf *out, strbuf_addf(out, " into %s\n", current_branch); } -static int do_fmt_merge_msg(int merge_title, int merge_summary, - struct strbuf *in, struct strbuf *out) { - int limit = 20, i = 0, pos = 0; +static int do_fmt_merge_msg(int merge_title, struct strbuf *in, + struct strbuf *out, int shortlog_len) { + int i = 0, pos = 0; unsigned char head_sha1[20]; const char *current_branch; @@ -288,7 +289,7 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, if (merge_title) do_fmt_merge_msg_title(out, current_branch); - if (merge_summary) { + if (shortlog_len) { struct commit *head; struct rev_info rev; @@ -303,17 +304,14 @@ static int do_fmt_merge_msg(int merge_title, int merge_summary, for (i = 0; i < origins.nr; i++) shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, limit, out); + head, &rev, shortlog_len, out); } return 0; } -int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { - return do_fmt_merge_msg(1, merge_summary, in, out); -} - -int fmt_merge_msg_shortlog(struct strbuf *in, struct strbuf *out) { - return do_fmt_merge_msg(0, 1, in, out); +int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + int merge_title, int shortlog_len) { + return do_fmt_merge_msg(merge_title, in, out, shortlog_len); } int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) @@ -321,10 +319,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) const char *inpath = NULL; const char *message = NULL; struct option options[] = { - OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), - { OPTION_BOOLEAN, 0, "summary", &merge_summary, NULL, + { OPTION_INTEGER, 0, "log", &shortlog_len, "n", + "populate log with at most <n> entries from shortlog", + PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, + { OPTION_INTEGER, 0, "summary", &shortlog_len, "n", "alias for --log (deprecated)", - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN }, + PARSE_OPT_OPTARG | PARSE_OPT_HIDDEN, NULL, + DEFAULT_MERGE_LOG_LEN }, OPT_STRING('m', "message", &message, "text", "use <text> as start of message"), OPT_FILENAME('F', "file", &inpath, "file to read from"), @@ -340,12 +341,14 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); - if (message && !merge_summary) { + if (message && !shortlog_len) { char nl = '\n'; write_in_full(STDOUT_FILENO, message, strlen(message)); write_in_full(STDOUT_FILENO, &nl, 1); return 0; } + if (shortlog_len < 0) + die("Negative --log=%d", shortlog_len); if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -355,12 +358,13 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (strbuf_read(&input, fileno(in), 0) < 0) die_errno("could not read input file"); - if (message) { + + if (message) strbuf_addstr(&output, message); - ret = fmt_merge_msg_shortlog(&input, &output); - } else { - ret = fmt_merge_msg(merge_summary, &input, &output); - } + ret = fmt_merge_msg(&input, &output, + message ? 0 : 1, + shortlog_len); + if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/log.c b/builtin/log.c index eaa1ee0fa7..22d12903ac 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1056,8 +1056,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; rev.diff = 1; - rev.combine_merges = 0; - rev.ignore_merges = 1; + rev.no_merges = 1; DIFF_OPT_SET(&rev.diffopt, RECURSIVE); rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); @@ -1228,10 +1227,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) continue; } - /* ignore merges */ - if (commit->parents && commit->parents->next) - continue; - if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; diff --git a/builtin/merge.c b/builtin/merge.c index 5f65c0c8a6..2dba3b9901 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -42,7 +42,7 @@ static const char * const builtin_merge_usage[] = { NULL }; -static int show_diffstat = 1, option_log, squash; +static int show_diffstat = 1, shortlog_len, squash; static int option_commit = 1, allow_fast_forward = 1; static int fast_forward_only; static int allow_trivial = 1, have_message; @@ -177,8 +177,9 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "stat", &show_diffstat, "show a diffstat at the end of the merge"), OPT_BOOLEAN(0, "summary", &show_diffstat, "(synonym to --stat)"), - OPT_BOOLEAN(0, "log", &option_log, - "add list of one-line log to merge commit message"), + { OPTION_INTEGER, 0, "log", &shortlog_len, "n", + "add (at most <n>) entries from shortlog to merge commit message", + PARSE_OPT_OPTARG, NULL, DEFAULT_MERGE_LOG_LEN }, OPT_BOOLEAN(0, "squash", &squash, "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, @@ -504,10 +505,17 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_twohead, k, v); else if (!strcmp(k, "pull.octopus")) return git_config_string(&pull_octopus, k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) - option_log = git_config_bool(k, v); else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); + else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { + int is_bool; + shortlog_len = git_config_bool_or_int(k, v, &is_bool); + if (!is_bool && shortlog_len < 0) + return error("%s: negative length %s", k, v); + if (is_bool && shortlog_len) + shortlog_len = DEFAULT_MERGE_LOG_LEN; + return 0; + } return git_diff_ui_config(k, v, cb); } @@ -1012,14 +1020,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) merge_name(argv[i], &merge_names); - if (have_message && option_log) - fmt_merge_msg_shortlog(&merge_names, &merge_msg); - else if (!have_message) - fmt_merge_msg(option_log, &merge_names, &merge_msg); - - - if (!(have_message && !option_log) && merge_msg.len) - strbuf_setlen(&merge_msg, merge_msg.len-1); + if (!have_message || shortlog_len) { + fmt_merge_msg(&merge_names, &merge_msg, !have_message, + shortlog_len); + if (merge_msg.len) + strbuf_setlen(&merge_msg, merge_msg.len - 1); + } } if (head_invalid || !argc) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 9ad1e66916..eb1e3e7467 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -16,6 +16,7 @@ #include "resolve-undo.h" static int nr_trees; +static int read_empty; static struct tree *trees[MAX_UNPACK_TREES]; static int list_tree(unsigned char *sha1) @@ -32,7 +33,7 @@ static int list_tree(unsigned char *sha1) } static const char * const read_tree_usage[] = { - "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]", + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--no-sparse-checkout] [--index-output=<file>] (--empty | <tree-ish1> [<tree-ish2> [<tree-ish3>]])", NULL }; @@ -106,6 +107,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { OPTION_CALLBACK, 0, "index-output", NULL, "FILE", "write resulting index to <FILE>", PARSE_OPT_NONEG, index_output_cb }, + OPT_SET_INT(0, "empty", &read_empty, + "only empty the index", 1), OPT__VERBOSE(&opts.verbose_update), OPT_GROUP("Merging"), OPT_SET_INT('m', NULL, &opts.merge, @@ -166,6 +169,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } + if (nr_trees == 0 && !read_empty) + warning("read-tree: emptying the index with no arguments is deprecated; use --empty"); + else if (nr_trees > 0 && read_empty) + die("passing trees as arguments contradicts --empty"); + if (1 < opts.index_only + opts.update) die("-u and -i at the same time makes no sense"); if ((opts.update||opts.index_only) && !opts.merge) diff --git a/compat/mingw.c b/compat/mingw.c index f2d9e1fd97..6590f33cc8 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -127,7 +127,7 @@ int mingw_open (const char *filename, int oflags, ...) mode = va_arg(args, int); va_end(args); - if (!strcmp(filename, "/dev/null")) + if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; fd = open(filename, oflags, mode); @@ -160,7 +160,7 @@ ssize_t mingw_write(int fd, const void *buf, size_t count) #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { - if (!strcmp(filename, "/dev/null")) + if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; return fopen(filename, otype); } @@ -192,8 +192,11 @@ static inline time_t filetime_to_time_t(const FILETIME *ft) /* We keep the do_lstat code in a separate function to avoid recursion. * When a path ends with a slash, the stat will fail with ENOENT. In * this case, we strip the trailing slashes and stat again. + * + * If follow is true then act like stat() and report on the link + * target. Otherwise report on the link itself. */ -static int do_lstat(const char *file_name, struct stat *buf) +static int do_lstat(int follow, const char *file_name, struct stat *buf) { WIN32_FILE_ATTRIBUTE_DATA fdata; @@ -209,6 +212,25 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + WIN32_FIND_DATAA findbuf; + HANDLE handle = FindFirstFileA(file_name, &findbuf); + if (handle != INVALID_HANDLE_VALUE) { + if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && + (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { + if (follow) { + char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + } else { + buf->st_mode = S_IFLNK; + } + buf->st_mode |= S_IREAD; + if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + buf->st_mode |= S_IWRITE; + } + FindClose(handle); + } + } return 0; } return -1; @@ -220,12 +242,12 @@ static int do_lstat(const char *file_name, struct stat *buf) * complete. Note that Git stat()s are redirected to mingw_lstat() * too, since Windows doesn't really handle symlinks that well. */ -int mingw_lstat(const char *file_name, struct stat *buf) +static int do_stat_internal(int follow, const char *file_name, struct stat *buf) { int namelen; static char alt_name[PATH_MAX]; - if (!do_lstat(file_name, buf)) + if (!do_lstat(follow, file_name, buf)) return 0; /* if file_name ended in a '/', Windows returned ENOENT; @@ -244,7 +266,16 @@ int mingw_lstat(const char *file_name, struct stat *buf) memcpy(alt_name, file_name, namelen); alt_name[namelen] = 0; - return do_lstat(alt_name, buf); + return do_lstat(follow, alt_name, buf); +} + +int mingw_lstat(const char *file_name, struct stat *buf) +{ + return do_stat_internal(0, file_name, buf); +} +int mingw_stat(const char *file_name, struct stat *buf) +{ + return do_stat_internal(1, file_name, buf); } #undef fstat @@ -873,6 +904,11 @@ void mingw_execvp(const char *cmd, char *const *argv) free_path_split(path); } +void mingw_execv(const char *cmd, char *const *argv) +{ + mingw_execve(cmd, argv, environ); +} + static char **copy_environ(void) { char **env; @@ -1386,6 +1422,7 @@ void mingw_open_html(const char *unixpath) const char *, const char *, const char *, INT); T ShellExecute; HMODULE shell32; + int r; shell32 = LoadLibrary("shell32.dll"); if (!shell32) @@ -1395,9 +1432,12 @@ void mingw_open_html(const char *unixpath) die("cannot run browser"); printf("Launching default browser to display HTML ...\n"); - ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); - + r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL); FreeLibrary(shell32); + /* see the MSDN documentation referring to the result codes here */ + if (r <= 32) { + die("failed to launch browser for %.*s", MAX_PATH, unixpath); + } } int link(const char *oldpath, const char *newpath) diff --git a/compat/mingw.h b/compat/mingw.h index 3b2477be5f..83e35e833b 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -6,17 +6,30 @@ */ typedef int pid_t; +typedef int uid_t; #define hstrerror strerror #define S_IFLNK 0120000 /* Symbolic link */ #define S_ISLNK(x) (((x) & S_IFMT) == S_IFLNK) #define S_ISSOCK(x) 0 + +#ifndef _STAT_H_ +#define S_IRUSR 0 +#define S_IWUSR 0 +#define S_IXUSR 0 +#define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +#endif #define S_IRGRP 0 #define S_IWGRP 0 #define S_IXGRP 0 -#define S_ISGID 0 +#define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) #define S_IROTH 0 +#define S_IWOTH 0 #define S_IXOTH 0 +#define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#define S_ISUID 0 +#define S_ISGID 0 +#define S_ISVTX 0 #define WIFEXITED(x) 1 #define WIFSIGNALED(x) 0 @@ -66,6 +79,12 @@ struct itimerval { #define ITIMER_REAL 0 /* + * sanitize preprocessor namespace polluted by Windows headers defining + * macros which collide with git local versions + */ +#undef HELP_COMMAND /* from winuser.h */ + +/* * trivial stubs */ @@ -75,17 +94,17 @@ static inline int symlink(const char *oldpath, const char *newpath) { errno = ENOSYS; return -1; } static inline int fchmod(int fildes, mode_t mode) { errno = ENOSYS; return -1; } -static inline int fork(void) +static inline pid_t fork(void) { errno = ENOSYS; return -1; } static inline unsigned int alarm(unsigned int seconds) { return 0; } static inline int fsync(int fd) { return _commit(fd); } -static inline int getppid(void) +static inline pid_t getppid(void) { return 1; } static inline void sync(void) {} -static inline int getuid() +static inline uid_t getuid(void) { return 1; } static inline struct passwd *getpwnam(const char *name) { return NULL; } @@ -117,7 +136,7 @@ static inline int mingw_unlink(const char *pathname) } #define unlink mingw_unlink -static inline int waitpid(pid_t pid, int *status, unsigned options) +static inline pid_t waitpid(pid_t pid, int *status, unsigned options) { if (options == 0) return _cwait(status, pid, 0); @@ -158,7 +177,7 @@ int poll(struct pollfd *ufds, unsigned int nfds, int timeout); struct tm *gmtime_r(const time_t *timep, struct tm *result); struct tm *localtime_r(const time_t *timep, struct tm *result); int getpagesize(void); /* defined in MinGW's libgcc.a */ -struct passwd *getpwuid(int uid); +struct passwd *getpwuid(uid_t uid); int setitimer(int type, struct itimerval *in, struct itimerval *out); int sigaction(int sig, struct sigaction *in, struct sigaction *out); int link(const char *oldpath, const char *newpath); @@ -222,10 +241,11 @@ int mingw_getpagesize(void); #ifndef ALREADY_DECLARED_STAT_FUNCS #define stat _stati64 int mingw_lstat(const char *file_name, struct stat *buf); +int mingw_stat(const char *file_name, struct stat *buf); int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat -#define _stati64(x,y) mingw_lstat(x,y) +#define _stati64(x,y) mingw_stat(x,y) #endif int mingw_utime(const char *file_name, const struct utimbuf *times); @@ -236,6 +256,8 @@ pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env, int fhin, int fhout, int fherr); void mingw_execvp(const char *cmd, char *const *argv); #define execvp mingw_execvp +void mingw_execv(const char *cmd, char *const *argv); +#define execv mingw_execv static inline unsigned int git_ntohl(unsigned int x) { return (unsigned int)ntohl(x); } diff --git a/configure.ac b/configure.ac index 56731c35c9..cc55b6d4f7 100644 --- a/configure.ac +++ b/configure.ac @@ -282,7 +282,15 @@ GIT_PARSE_WITH(iconv)) GIT_PARSE_WITH_SET_MAKE_VAR(gitconfig, ETC_GITCONFIG, Use VALUE instead of /etc/gitconfig as the global git configuration file. - If VALUE is not fully qualified it will be interpretted + If VALUE is not fully qualified it will be interpreted + as a path relative to the computed prefix at runtime.) + +# +# Allow user to set ETC_GITATTRIBUTES variable +GIT_PARSE_WITH_SET_MAKE_VAR(gitattributes, ETC_GITATTRIBUTES, + Use VALUE instead of /etc/gitattributes as the + global git attributes file. + If VALUE is not fully qualified it will be interpreted as a path relative to the computed prefix at runtime.) # diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README new file mode 100644 index 0000000000..438463b160 --- /dev/null +++ b/contrib/git-shell-commands/README @@ -0,0 +1,18 @@ +Sample programs callable through git-shell. Place a directory named +'git-shell-commands' in the home directory of a user whose shell is +git-shell. Then anyone logging in as that user will be able to run +executables in the 'git-shell-commands' directory. + +Provided commands: + +help: Prints out the names of available commands. When run +interactively, git-shell will automatically run 'help' on startup, +provided it exists. + +list: Displays any bare repository whose name ends with ".git" under +user's home directory. No other git repositories are visible, +although they might be clonable through git-shell. 'list' is designed +to minimize the number of calls to git that must be made in finding +available repositories; if your setup has additional repositories that +should be user-discoverable, you may wish to modify 'list' +accordingly. diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help new file mode 100755 index 0000000000..535770c6ec --- /dev/null +++ b/contrib/git-shell-commands/help @@ -0,0 +1,18 @@ +#!/bin/sh + +if tty -s +then + echo "Run 'help' for help, or 'exit' to leave. Available commands:" +else + echo "Run 'help' for help. Available commands:" +fi + +cd "$(dirname "$0")" + +for cmd in * +do + case "$cmd" in + help) ;; + *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;; + esac +done diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list new file mode 100755 index 0000000000..6f89938821 --- /dev/null +++ b/contrib/git-shell-commands/list @@ -0,0 +1,10 @@ +#!/bin/sh + +print_if_bare_repo=' + if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true + then + printf "%s\n" "${1#./}" + fi +' + +find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null @@ -3271,12 +3271,17 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if ((argcount = short_opt('S', av, &optarg))) { options->pickaxe = optarg; + options->pickaxe_opts |= DIFF_PICKAXE_KIND_S; + return argcount; + } else if ((argcount = short_opt('G', av, &optarg))) { + options->pickaxe = optarg; + options->pickaxe_opts |= DIFF_PICKAXE_KIND_G; return argcount; } else if (!strcmp(arg, "--pickaxe-all")) - options->pickaxe_opts = DIFF_PICKAXE_ALL; + options->pickaxe_opts |= DIFF_PICKAXE_ALL; else if (!strcmp(arg, "--pickaxe-regex")) - options->pickaxe_opts = DIFF_PICKAXE_REGEX; + options->pickaxe_opts |= DIFF_PICKAXE_REGEX; else if ((argcount = short_opt('O', av, &optarg))) { options->orderfile = optarg; return argcount; @@ -4176,7 +4181,7 @@ void diffcore_std(struct diff_options *options) diffcore_merge_broken(); } if (options->pickaxe) - diffcore_pickaxe(options->pickaxe, options->pickaxe_opts); + diffcore_pickaxe(options); if (options->orderfile) diffcore_order(options->orderfile); if (!options->found_follow) @@ -238,6 +238,9 @@ extern int diff_setup_done(struct diff_options *); #define DIFF_PICKAXE_ALL 1 #define DIFF_PICKAXE_REGEX 2 +#define DIFF_PICKAXE_KIND_S 4 /* traditional plumbing counter */ +#define DIFF_PICKAXE_KIND_G 8 /* grep in the patch */ + extern void diffcore_std(struct diff_options *); extern void diffcore_fix_diff_index(struct diff_options *); diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index 9c6544daac..38570999c3 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -1,9 +1,148 @@ /* * Copyright (C) 2005 Junio C Hamano + * Copyright (C) 2010 Google Inc. */ #include "cache.h" #include "diff.h" #include "diffcore.h" +#include "xdiff-interface.h" + +struct diffgrep_cb { + regex_t *regexp; + int hit; +}; + +static void diffgrep_consume(void *priv, char *line, unsigned long len) +{ + struct diffgrep_cb *data = priv; + regmatch_t regmatch; + int hold; + + if (line[0] != '+' && line[0] != '-') + return; + if (data->hit) + /* + * NEEDSWORK: we should have a way to terminate the + * caller early. + */ + return; + /* Yuck -- line ought to be "const char *"! */ + hold = line[len]; + line[len] = '\0'; + data->hit = !regexec(data->regexp, line + 1, 1, ®match, 0); + line[len] = hold; +} + +static void fill_one(struct diff_filespec *one, + mmfile_t *mf, struct userdiff_driver **textconv) +{ + if (DIFF_FILE_VALID(one)) { + *textconv = get_textconv(one); + mf->size = fill_textconv(*textconv, one, &mf->ptr); + } else { + memset(mf, 0, sizeof(*mf)); + } +} + +static int diff_grep(struct diff_filepair *p, regex_t *regexp, struct diff_options *o) +{ + regmatch_t regmatch; + struct userdiff_driver *textconv_one = NULL; + struct userdiff_driver *textconv_two = NULL; + mmfile_t mf1, mf2; + int hit; + + if (diff_unmodified_pair(p)) + return 0; + + fill_one(p->one, &mf1, &textconv_one); + fill_one(p->two, &mf2, &textconv_two); + + if (!mf1.ptr) { + if (!mf2.ptr) + return 0; /* ignore unmerged */ + /* created "two" -- does it have what we are looking for? */ + hit = !regexec(regexp, p->two->data, 1, ®match, 0); + } else if (!mf2.ptr) { + /* removed "one" -- did it have what we are looking for? */ + hit = !regexec(regexp, p->one->data, 1, ®match, 0); + } else { + /* + * We have both sides; need to run textual diff and see if + * the pattern appears on added/deleted lines. + */ + struct diffgrep_cb ecbdata; + xpparam_t xpp; + xdemitconf_t xecfg; + + memset(&xpp, 0, sizeof(xpp)); + memset(&xecfg, 0, sizeof(xecfg)); + ecbdata.regexp = regexp; + ecbdata.hit = 0; + xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; + xdi_diff_outf(&mf1, &mf2, diffgrep_consume, &ecbdata, + &xpp, &xecfg); + hit = ecbdata.hit; + } + if (textconv_one) + free(mf1.ptr); + if (textconv_two) + free(mf2.ptr); + return hit; +} + +static void diffcore_pickaxe_grep(struct diff_options *o) +{ + struct diff_queue_struct *q = &diff_queued_diff; + int i, has_changes, err; + regex_t regex; + struct diff_queue_struct outq; + outq.queue = NULL; + outq.nr = outq.alloc = 0; + + err = regcomp(®ex, o->pickaxe, REG_EXTENDED | REG_NEWLINE); + if (err) { + char errbuf[1024]; + regerror(err, ®ex, errbuf, 1024); + regfree(®ex); + die("invalid log-grep regex: %s", errbuf); + } + + if (o->pickaxe_opts & DIFF_PICKAXE_ALL) { + /* Showing the whole changeset if needle exists */ + for (i = has_changes = 0; !has_changes && i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (diff_grep(p, ®ex, o)) + has_changes++; + } + if (has_changes) + return; /* do not munge the queue */ + + /* + * Otherwise we will clear the whole queue by copying + * the empty outq at the end of this function, but + * first clear the current entries in the queue. + */ + for (i = 0; i < q->nr; i++) + diff_free_filepair(q->queue[i]); + } else { + /* Showing only the filepairs that has the needle */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + if (diff_grep(p, ®ex, o)) + diff_q(&outq, p); + else + diff_free_filepair(p); + } + } + + regfree(®ex); + + free(q->queue); + *q = outq; + return; +} static unsigned int contains(struct diff_filespec *one, const char *needle, unsigned long len, @@ -48,8 +187,10 @@ static unsigned int contains(struct diff_filespec *one, return cnt; } -void diffcore_pickaxe(const char *needle, int opts) +static void diffcore_pickaxe_count(struct diff_options *o) { + const char *needle = o->pickaxe; + int opts = o->pickaxe_opts; struct diff_queue_struct *q = &diff_queued_diff; unsigned long len = strlen(needle); int i, has_changes; @@ -135,3 +276,12 @@ void diffcore_pickaxe(const char *needle, int opts) *q = outq; return; } + +void diffcore_pickaxe(struct diff_options *o) +{ + /* Might want to warn when both S and G are on; I don't care... */ + if (o->pickaxe_opts & DIFF_PICKAXE_KIND_G) + return diffcore_pickaxe_grep(o); + else + return diffcore_pickaxe_count(o); +} diff --git a/diffcore.h b/diffcore.h index 8b3241ad13..b8f1fdecf4 100644 --- a/diffcore.h +++ b/diffcore.h @@ -107,7 +107,7 @@ extern void diff_q(struct diff_queue_struct *, struct diff_filepair *); extern void diffcore_break(int); extern void diffcore_rename(struct diff_options *); extern void diffcore_merge_broken(void); -extern void diffcore_pickaxe(const char *needle, int opts); +extern void diffcore_pickaxe(struct diff_options *); extern void diffcore_order(const char *orderfile); #define DIFF_DEBUG 0 @@ -444,12 +444,12 @@ else set x first= } - case "$arg" in - /*) - set "$@" "$arg" ;; - *) - set "$@" "$prefix$arg" ;; - esac + if is_absolute_path "$arg" + then + set "$@" "$arg" + else + set "$@" "$prefix$arg" + fi done shift fi diff --git a/git-cvsserver.perl b/git-cvsserver.perl index e9f3037df3..bd19b85533 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -8,7 +8,7 @@ #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### #### Released under the GNU Public License, version 2. @@ -2680,7 +2680,7 @@ package GITCVS::log; #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### @@ -2847,7 +2847,7 @@ package GITCVS::updater; #### Copyright The Open University UK - 2006. #### #### Authors: Martyn Smith <martyn@catalyst.net.nz> -#### Martin Langhoff <martin@catalyst.net.nz> +#### Martin Langhoff <martin@laptop.org> #### #### diff --git a/git-merge-octopus.sh b/git-merge-octopus.sh index 615753c83c..8643f74cb0 100755 --- a/git-merge-octopus.sh +++ b/git-merge-octopus.sh @@ -61,6 +61,11 @@ do esac eval pretty_name=\${GITHEAD_$SHA1:-$SHA1} + if test "$SHA1" = "$pretty_name" + then + SHA1_UP="$(echo "$SHA1" | tr a-z A-Z)" + eval pretty_name=\${GITHEAD_$SHA1_UP:-$pretty_name} + fi common=$(git merge-base --all $SHA1 $MRC) || die "Unable to find common commit with $pretty_name" diff --git a/git-send-email.perl b/git-send-email.perl index e1f29a72a1..8cc4161155 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -24,6 +24,7 @@ use Text::ParseWords; use Data::Dumper; use Term::ANSIColor; use File::Temp qw/ tempdir tempfile /; +use File::Spec::Functions qw(catfile); use Error qw(:try); use Git; @@ -85,6 +86,7 @@ git send-email [options] <file | directory | rev-list options > --[no-]validate * Perform patch sanity checks. Default on. --[no-]format-patch * understand any non optional arguments as `git format-patch` ones. + --force * Send even if safety checks would prevent it. EOT exit(1); @@ -162,6 +164,7 @@ if ($@) { my ($quiet, $dry_run) = (0, 0); my $format_patch; my $compose_filename; +my $force = 0; # Handle interactive edition of files. my $multiedit; @@ -301,6 +304,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "validate!" => \$validate, "format-patch!" => \$format_patch, "8bit-encoding=s" => \$auto_8bit_encoding, + "force" => \$force, ); unless ($rc) { @@ -511,7 +515,7 @@ while (defined(my $f = shift @ARGV)) { opendir(DH,$f) or die "Failed to opendir $f: $!"; - push @files, grep { -f $_ } map { +$f . "/" . $_ } + push @files, grep { -f $_ } map { catfile($f, $_) } sort readdir(DH); closedir(DH); } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) { @@ -702,6 +706,16 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { default => "UTF-8"); } +if (!$force) { + for my $f (@files) { + if (get_patch_subject($f) =~ /\*\*\* SUBJECT HERE \*\*\*/) { + die "Refusing to send because the patch\n\t$f\n" + . "has the template subject '*** SUBJECT HERE ***'. " + . "Pass --force if you really want to send.\n"; + } + } +} + my $prompting = 0; if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 6131670860..58d30c9388 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -209,5 +209,20 @@ case $(uname -s) in find () { /usr/bin/find "$@" } + is_absolute_path () { + case "$1" in + [/\\]* | [A-Za-z]:*) + return 0 ;; + esac + return 1 + } ;; +*) + is_absolute_path () { + case "$1" in + /*) + return 0 ;; + esac + return 1 + } esac @@ -189,30 +189,74 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list) return compile_pattern_or(list); } -void compile_grep_patterns(struct grep_opt *opt) +static struct grep_expr *grep_true_expr(void) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = GREP_NODE_TRUE; + return z; +} + +static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = GREP_NODE_OR; + z->u.binary.left = left; + z->u.binary.right = right; + return z; +} + +static struct grep_expr *prep_header_patterns(struct grep_opt *opt) { struct grep_pat *p; - struct grep_expr *header_expr = NULL; - - if (opt->header_list) { - p = opt->header_list; - header_expr = compile_pattern_expr(&p); - if (p) - die("incomplete pattern expression: %s", p->pattern); - for (p = opt->header_list; p; p = p->next) { - switch (p->token) { - case GREP_PATTERN: /* atom */ - case GREP_PATTERN_HEAD: - case GREP_PATTERN_BODY: - compile_regexp(p, opt); - break; - default: - opt->extended = 1; - break; - } + struct grep_expr *header_expr; + struct grep_expr *(header_group[GREP_HEADER_FIELD_MAX]); + enum grep_header_field fld; + + if (!opt->header_list) + return NULL; + p = opt->header_list; + for (p = opt->header_list; p; p = p->next) { + if (p->token != GREP_PATTERN_HEAD) + die("bug: a non-header pattern in grep header list."); + if (p->field < 0 || GREP_HEADER_FIELD_MAX <= p->field) + die("bug: unknown header field %d", p->field); + compile_regexp(p, opt); + } + + for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++) + header_group[fld] = NULL; + + for (p = opt->header_list; p; p = p->next) { + struct grep_expr *h; + struct grep_pat *pp = p; + + h = compile_pattern_atom(&pp); + if (!h || pp != p->next) + die("bug: malformed header expr"); + if (!header_group[p->field]) { + header_group[p->field] = h; + continue; } + header_group[p->field] = grep_or_expr(h, header_group[p->field]); } + header_expr = NULL; + + for (fld = 0; fld < GREP_HEADER_FIELD_MAX; fld++) { + if (!header_group[fld]) + continue; + if (!header_expr) + header_expr = grep_true_expr(); + header_expr = grep_or_expr(header_group[fld], header_expr); + } + return header_expr; +} + +void compile_grep_patterns(struct grep_opt *opt) +{ + struct grep_pat *p; + struct grep_expr *header_expr = prep_header_patterns(opt); + for (p = opt->pattern_list; p; p = p->next) { switch (p->token) { case GREP_PATTERN: /* atom */ @@ -231,9 +275,6 @@ void compile_grep_patterns(struct grep_opt *opt) else if (!opt->extended) return; - /* Then bundle them up in an expression. - * A classic recursive descent parser would do. - */ p = opt->pattern_list; if (p) opt->pattern_expression = compile_pattern_expr(&p); @@ -243,22 +284,18 @@ void compile_grep_patterns(struct grep_opt *opt) if (!header_expr) return; - if (opt->pattern_expression) { - struct grep_expr *z; - z = xcalloc(1, sizeof(*z)); - z->node = GREP_NODE_OR; - z->u.binary.left = opt->pattern_expression; - z->u.binary.right = header_expr; - opt->pattern_expression = z; - } else { + if (!opt->pattern_expression) opt->pattern_expression = header_expr; - } + else + opt->pattern_expression = grep_or_expr(opt->pattern_expression, + header_expr); opt->all_match = 1; } static void free_pattern_expr(struct grep_expr *x) { switch (x->node) { + case GREP_NODE_TRUE: case GREP_NODE_ATOM: break; case GREP_NODE_NOT: @@ -487,6 +524,9 @@ static int match_expr_eval(struct grep_expr *x, char *bol, char *eol, if (!x) die("Not a valid grep expression"); switch (x->node) { + case GREP_NODE_TRUE: + h = 1; + break; case GREP_NODE_ATOM: h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0); break; @@ -22,6 +22,7 @@ enum grep_header_field { GREP_HEADER_AUTHOR = 0, GREP_HEADER_COMMITTER }; +#define GREP_HEADER_FIELD_MAX (GREP_HEADER_COMMITTER + 1) struct grep_pat { struct grep_pat *next; @@ -41,6 +42,7 @@ enum grep_expr_node { GREP_NODE_ATOM, GREP_NODE_NOT, GREP_NODE_AND, + GREP_NODE_TRUE, GREP_NODE_OR }; @@ -2,6 +2,10 @@ #include "quote.h" #include "exec_cmd.h" #include "strbuf.h" +#include "run-command.h" + +#define COMMAND_DIR "git-shell-commands" +#define HELP_COMMAND COMMAND_DIR "/help" static int do_generic_cmd(const char *me, char *arg) { @@ -33,6 +37,86 @@ static int do_cvs_cmd(const char *me, char *arg) return execv_git_cmd(cvsserver_argv); } +static int is_valid_cmd_name(const char *cmd) +{ + /* Test command contains no . or / characters */ + return cmd[strcspn(cmd, "./")] == '\0'; +} + +static char *make_cmd(const char *prog) +{ + char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2)); + strcpy(prefix, COMMAND_DIR); + strcat(prefix, "/"); + strcat(prefix, prog); + return prefix; +} + +static void cd_to_homedir(void) +{ + const char *home = getenv("HOME"); + if (!home) + die("could not determine user's home directory; HOME is unset"); + if (chdir(home) == -1) + die("could not chdir to user's home directory"); +} + +static void run_shell(void) +{ + int done = 0; + static const char *help_argv[] = { HELP_COMMAND, NULL }; + /* Print help if enabled */ + run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE); + + do { + struct strbuf line = STRBUF_INIT; + const char *prog; + char *full_cmd; + char *rawargs; + char *split_args; + const char **argv; + int code; + int count; + + fprintf(stderr, "git> "); + if (strbuf_getline(&line, stdin, '\n') == EOF) { + fprintf(stderr, "\n"); + strbuf_release(&line); + break; + } + strbuf_trim(&line); + rawargs = strbuf_detach(&line, NULL); + split_args = xstrdup(rawargs); + count = split_cmdline(split_args, &argv); + if (count < 0) { + fprintf(stderr, "invalid command format '%s': %s\n", rawargs, + split_cmdline_strerror(count)); + free(split_args); + free(rawargs); + continue; + } + + prog = argv[0]; + if (!strcmp(prog, "")) { + } else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") || + !strcmp(prog, "exit") || !strcmp(prog, "bye")) { + done = 1; + } else if (is_valid_cmd_name(prog)) { + full_cmd = make_cmd(prog); + argv[0] = full_cmd; + code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE); + if (code == -1 && errno == ENOENT) { + fprintf(stderr, "unrecognized command '%s'\n", prog); + } + free(full_cmd); + } else { + fprintf(stderr, "invalid command format '%s'\n", prog); + } + + free(argv); + free(rawargs); + } while (!done); +} static struct commands { const char *name; @@ -48,8 +132,10 @@ static struct commands { int main(int argc, char **argv) { char *prog; + const char **user_argv; struct commands *cmd; int devnull_fd; + int count; /* * Always open file descriptors 0/1/2 to avoid clobbering files @@ -66,17 +152,28 @@ int main(int argc, char **argv) /* * Special hack to pretend to be a CVS server */ - if (argc == 2 && !strcmp(argv[1], "cvs server")) + if (argc == 2 && !strcmp(argv[1], "cvs server")) { argv--; + } else if (argc == 1) { + /* Allow the user to run an interactive shell */ + cd_to_homedir(); + if (access(COMMAND_DIR, R_OK | X_OK) == -1) { + die("Interactive git shell is not enabled.\n" + "hint: ~/" COMMAND_DIR " should exist " + "and have read and execute access."); + } + run_shell(); + exit(0); + } else if (argc != 3 || strcmp(argv[1], "-c")) { + /* + * We do not accept any other modes except "-c" followed by + * "cmd arg", where "cmd" is a very limited subset of git + * commands or a command in the COMMAND_DIR + */ + die("Run with no arguments or with -c cmd"); + } - /* - * We do not accept anything but "-c" followed by "cmd arg", - * where "cmd" is a very limited subset of git commands. - */ - else if (argc != 3 || strcmp(argv[1], "-c")) - die("What do you think I am? A shell?"); - - prog = argv[2]; + prog = xstrdup(argv[2]); if (!strncmp(prog, "git", 3) && isspace(prog[3])) /* Accept "git foo" as if the caller said "git-foo". */ prog[3] = '-'; @@ -99,5 +196,21 @@ int main(int argc, char **argv) } exit(cmd->exec(cmd->name, arg)); } - die("unrecognized command '%s'", prog); + + cd_to_homedir(); + count = split_cmdline(prog, &user_argv); + if (count >= 0) { + if (is_valid_cmd_name(user_argv[0])) { + prog = make_cmd(user_argv[0]); + user_argv[0] = prog; + execv(user_argv[0], (char *const *) user_argv); + } + free(prog); + free(user_argv); + die("unrecognized command '%s'", argv[2]); + } else { + free(prog); + die("invalid command format '%s': %s", argv[2], + split_cmdline_strerror(count)); + } } @@ -399,6 +399,8 @@ int strbuf_branchname(struct strbuf *sb, const char *name) int strbuf_check_branch_ref(struct strbuf *sb, const char *name) { strbuf_branchname(sb, name); + if (name[0] == '-') + return CHECK_REF_FORMAT_ERROR; strbuf_splice(sb, 0, 0, "refs/heads/", 11); return check_ref_format(sb->buf); } diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 81ef2a0969..8c490c8707 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -19,9 +19,9 @@ our \$site_name = '[localhost]'; our \$site_header = ''; our \$site_footer = ''; our \$home_text = 'indextext.html'; -our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/static/gitweb.css'); -our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/static/git-logo.png'; -our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/static/git-favicon.png'; +our @stylesheets = ('file:///$GIT_BUILD_DIR/gitweb/static/gitweb.css'); +our \$logo = 'file:///$GIT_BUILD_DIR/gitweb/static/git-logo.png'; +our \$favicon = 'file:///$GIT_BUILD_DIR/gitweb/static/git-favicon.png'; our \$projects_list = ''; our \$export_ok = ''; our \$strict_export = ''; @@ -38,7 +38,7 @@ gitweb_run () { GATEWAY_INTERFACE='CGI/1.1' HTTP_ACCEPT='*/*' REQUEST_METHOD='GET' - SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl" + SCRIPT_NAME="$GIT_BUILD_DIR/gitweb/gitweb.perl" QUERY_STRING=""$1"" PATH_INFO=""$2"" export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \ @@ -81,8 +81,8 @@ if ! test_have_prereq PERL; then fi perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { - skip_all='skipping gitweb tests, perl version is too old' - test_done + skip_all='skipping gitweb tests, perl version is too old' + test_done } gitweb_init diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index de38c7f7aa..25205ac0ee 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -36,6 +36,9 @@ test_expect_success 'setup' ' echo "d/* test=a/b/d/*" echo "d/yes notest" ) >a/b/.gitattributes + ( + echo "global test=global" + ) >$HOME/global-gitattributes ' @@ -57,6 +60,16 @@ test_expect_success 'attribute test' ' ' +test_expect_success 'core.attributesfile' ' + attr_check global unspecified && + git config core.attributesfile "$HOME/global-gitattributes" && + attr_check global global && + git config core.attributesfile "~/global-gitattributes" && + attr_check global global && + echo "global test=precedence" >> .gitattributes && + attr_check global precedence +' + test_expect_success 'attribute test: read paths from stdin' ' cat <<EOF > expect diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 074f2f2e3e..d0ab8ffe1b 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -701,13 +701,13 @@ cat >expect <<\EOF trailingtilde = foo~ EOF -test_expect_success 'set --path' ' +test_expect_success NOT_MINGW 'set --path' ' git config --path path.home "~/" && git config --path path.normal "/dev/null" && git config --path path.trailingtilde "foo~" && test_cmp expect .git/config' -if test "${HOME+set}" +if test_have_prereq NOT_MINGW && test "${HOME+set}" then test_set_prereq HOMEVAR fi @@ -730,7 +730,7 @@ cat >expect <<\EOF foo~ EOF -test_expect_success 'get --path copes with unset $HOME' ' +test_expect_success NOT_MINGW 'get --path copes with unset $HOME' ' ( unset HOME; test_must_fail git config --get --path path.home \ diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 19857f4326..9a66520588 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -210,6 +210,9 @@ log -m -p master log -SF master log -S F master log -SF -p master +log -GF master +log -GF -p master +log -GF -p --pickaxe-all master log --decorate --all log --decorate=full --all diff --git a/t/t4013/diff.log_-GF_-p_--pickaxe-all_master b/t/t4013/diff.log_-GF_-p_--pickaxe-all_master new file mode 100644 index 0000000000..d36f88098b --- /dev/null +++ b/t/t4013/diff.log_-GF_-p_--pickaxe-all_master @@ -0,0 +1,27 @@ +$ git log -GF -p --pickaxe-all master +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C +$ diff --git a/t/t4013/diff.log_-GF_-p_master b/t/t4013/diff.log_-GF_-p_master new file mode 100644 index 0000000000..9d93f2c23a --- /dev/null +++ b/t/t4013/diff.log_-GF_-p_master @@ -0,0 +1,18 @@ +$ git log -GF -p master +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +$ diff --git a/t/t4013/diff.log_-GF_master b/t/t4013/diff.log_-GF_master new file mode 100644 index 0000000000..4c6708d2d0 --- /dev/null +++ b/t/t4013/diff.log_-GF_master @@ -0,0 +1,7 @@ +$ git log -GF master +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index f87434b9f8..07bf6eb49d 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -12,24 +12,29 @@ test_expect_success setup ' for i in 1 2 3 4 5 6 7 8 9 10; do echo "$i"; done >file && cat file >elif && git add file elif && + test_tick && git commit -m Initial && git checkout -b side && for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file && test_chmod +x elif && + test_tick && git commit -m "Side changes #1" && for i in D E F; do echo "$i"; done >>file && git update-index file && + test_tick && git commit -m "Side changes #2" && git tag C2 && for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >file && git update-index file && + test_tick && git commit -m "Side changes #3 with \\n backslash-n in it." && git checkout master && git diff-tree -p C2 | git apply --index && + test_tick && git commit -m "Master accepts moral equivalent of #2" ' @@ -51,6 +56,22 @@ test_expect_success "format-patch --ignore-if-in-upstream" ' ' +test_expect_success "format-patch doesn't consider merge commits" ' + + git checkout -b slave master && + echo "Another line" >>file && + test_tick && + git commit -am "Slave change #1" && + echo "Yet another line" >>file && + test_tick && + git commit -am "Slave change #2" && + git checkout -b merger master && + test_tick && + git merge --no-ff slave && + cnt=`git format-patch -3 --stdout | grep "^From " | wc -l` && + test $cnt = 3 +' + test_expect_success "format-patch result applies" ' git checkout -b rebuild-0 master && diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index c8e19372b0..0a61b57b5f 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -32,7 +32,7 @@ EOF sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java -builtin_patterns="bibtex cpp csharp html java objc pascal php python ruby tex" +builtin_patterns="bibtex cpp csharp fortran html java objc pascal php python ruby tex" for p in $builtin_patterns do test_expect_success "builtin $p pattern compiles" ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 27bfba55bd..cff1b3e050 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -94,7 +94,7 @@ test_expect_success 'git archive with --output' \ 'git archive --output=b4.tar HEAD && test_cmp b.tar b4.tar' -test_expect_success 'git archive --remote' \ +test_expect_success NOT_MINGW 'git archive --remote' \ 'git archive --remote=. HEAD >b5.tar && test_cmp b.tar b5.tar' diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 8a298a655f..b6b1fc30cd 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -4,14 +4,9 @@ test_description='test automatic tag following' . ./test-lib.sh -case $(uname -s) in -*MINGW*) +if !test_have_prereq NOT_MINGW; then say "GIT_DEBUG_SEND_PACK not supported - skipping tests" - ;; -*) - test_set_prereq NOT_MINGW - ;; -esac +fi # End state of the repository: # diff --git a/t/t5560-http-backend-noserver.sh b/t/t5560-http-backend-noserver.sh index 44885b850c..406432e7a5 100755 --- a/t/t5560-http-backend-noserver.sh +++ b/t/t5560-http-backend-noserver.sh @@ -5,11 +5,12 @@ test_description='test git-http-backend-noserver' HTTPD_DOCUMENT_ROOT_PATH="$TRASH_DIRECTORY" +test_have_prereq MINGW && export GREP_OPTIONS=-U + run_backend() { echo "$2" | QUERY_STRING="${1#*\?}" \ - GIT_PROJECT_ROOT="$HTTPD_DOCUMENT_ROOT_PATH" \ - PATH_INFO="${1%%\?*}" \ + PATH_TRANSLATED="$HTTPD_DOCUMENT_ROOT_PATH/${1%%\?*}" \ git http-backend >act.out 2>act.err } diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh index 52d0dc4bb8..460bf741b5 100755 --- a/t/t6038-merge-text-auto.sh +++ b/t/t6038-merge-text-auto.sh @@ -14,6 +14,8 @@ test_description='CRLF merge conflict across text=auto change . ./test-lib.sh +test_have_prereq MINGW && SED_OPTIONS=-b + test_expect_success setup ' git config core.autocrlf false && @@ -60,7 +62,7 @@ test_expect_success setup ' test_expect_success 'set up fuzz_conflict() helper' ' fuzz_conflict() { - sed -e "s/^\([<>=]......\) .*/\1/" "$@" + sed $SED_OPTIONS -e "s/^\([<>=]......\) .*/\1/" "$@" } ' diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 71f6cad3c2..9a16806921 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -129,6 +129,97 @@ test_expect_success '[merge] summary/log configuration' ' test_cmp expected actual2 ' +test_expect_success 'setup: clear [merge] configuration' ' + test_might_fail git config --unset-all merge.log && + test_might_fail git config --unset-all merge.summary +' + +test_expect_success 'setup FETCH_HEAD' ' + git checkout master && + test_tick && + git fetch . left +' + +test_expect_success 'merge.log=3 limits shortlog length' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} + + * left: (5 commits) + Left #5 + Left #4 + Left #3 + ... + EOF + + git -c merge.log=3 fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success 'merge.log=5 shows all 5 commits' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} + + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + git -c merge.log=5 fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success 'merge.log=0 disables shortlog' ' + echo "Merge branch ${apos}left${apos}" >expected + git -c merge.log=0 fmt-merge-msg <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success '--log=3 limits shortlog length' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} + + * left: (5 commits) + Left #5 + Left #4 + Left #3 + ... + EOF + + git fmt-merge-msg --log=3 <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success '--log=5 shows all 5 commits' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} + + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + git fmt-merge-msg --log=5 <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success '--no-log disables shortlog' ' + echo "Merge branch ${apos}left${apos}" >expected && + git fmt-merge-msg --no-log <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success '--log=0 disables shortlog' ' + echo "Merge branch ${apos}left${apos}" >expected && + git fmt-merge-msg --no-log <.git/FETCH_HEAD >actual && + test_cmp expected actual +' + test_expect_success 'fmt-merge-msg -m' ' echo "Sync with left" >expected && cat >expected.log <<-EOF && diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 023f225a4b..50658845ca 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -324,8 +324,13 @@ test_expect_success 'log grep setup' ' echo a >>file && test_tick && - git commit -a -m "third" + git commit -a -m "third" && + echo a >>file && + test_tick && + GIT_AUTHOR_NAME="Night Fall" \ + GIT_AUTHOR_EMAIL="nitfol@frobozz.com" \ + git commit -a -m "fourth" ' test_expect_success 'log grep (1)' ' @@ -372,6 +377,28 @@ test_expect_success 'log --grep --author implicitly uses all-match' ' test_cmp expect actual ' +test_expect_success 'log with multiple --author uses union' ' + git log --author="Thor" --author="Aster" --format=%s >actual && + { + echo third && echo second && echo initial + } >expect && + test_cmp expect actual +' + +test_expect_success 'log with --grep and multiple --author uses all-match' ' + git log --author="Thor" --author="Night" --grep=i --format=%s >actual && + { + echo third && echo initial + } >expect && + test_cmp expect actual +' + +test_expect_success 'log with --grep and multiple --author uses all-match' ' + git log --author="Thor" --author="Night" --grep=q --format=%s >actual && + >expect && + test_cmp expect actual +' + test_expect_success 'grep with CE_VALID file' ' git update-index --assume-unchanged t/t && rm t/t && diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 71b3df9b55..6f67da4e71 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1032,4 +1032,40 @@ test_expect_success $PREREQ '--8bit-encoding also treats subject' ' test_cmp expected actual ' +# Note that the patches in this test are deliberately out of order; we +# want to make sure it works even if the cover-letter is not in the +# first mail. +test_expect_success 'refusing to send cover letter template' ' + clean_fake_sendmail && + rm -fr outdir && + git format-patch --cover-letter -2 -o outdir && + test_must_fail git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0002-*.patch \ + outdir/0000-*.patch \ + outdir/0001-*.patch \ + 2>errors >out && + grep "SUBJECT HERE" errors && + test -z "$(ls msgtxt*)" +' + +test_expect_success '--force sends cover letter template anyway' ' + clean_fake_sendmail && + rm -fr outdir && + git format-patch --cover-letter -2 -o outdir && + git send-email \ + --force \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/0002-*.patch \ + outdir/0000-*.patch \ + outdir/0001-*.patch \ + 2>errors >out && + ! grep "SUBJECT HERE" errors && + test -n "$(ls msgtxt*)" +' + test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 830e5e7360..2af8f10c83 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -970,11 +970,13 @@ case $(uname -s) in # no POSIX permissions # backslashes in pathspec are converted to '/' # exec does not inherit the PID + test_set_prereq MINGW ;; *) test_set_prereq POSIXPERM test_set_prereq BSLASHPSPEC test_set_prereq EXECKEEPSPID + test_set_prereq NOT_MINGW ;; esac diff --git a/userdiff.c b/userdiff.c index e5522159b3..f9e05b548c 100644 --- a/userdiff.c +++ b/userdiff.c @@ -9,7 +9,23 @@ static int drivers_alloc; #define PATTERNS(name, pattern, word_regex) \ { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex } +#define IPATTERN(name, pattern, word_regex) \ + { name, NULL, -1, { pattern, REG_EXTENDED | REG_ICASE }, word_regex } static struct userdiff_driver builtin_drivers[] = { +IPATTERN("fortran", + "!^([C*]|[ \t]*!)\n" + "!^[ \t]*MODULE[ \t]+PROCEDURE[ \t]\n" + "^[ \t]*((END[ \t]+)?(PROGRAM|MODULE|BLOCK[ \t]+DATA" + "|([^'\" \t]+[ \t]+)*(SUBROUTINE|FUNCTION))[ \t]+[A-Z].*)$", + /* -- */ + "[a-zA-Z][a-zA-Z0-9_]*" + "|\\.([Ee][Qq]|[Nn][Ee]|[Gg][TtEe]|[Ll][TtEe]|[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]|[Aa][Nn][Dd]|[Oo][Rr]|[Nn]?[Ee][Qq][Vv]|[Nn][Oo][Tt])\\." + /* numbers and format statements like 2E14.4, or ES12.6, 9X. + * Don't worry about format statements without leading digits since + * they would have been matched above as a variable anyway. */ + "|[-+]?[0-9.]+([AaIiDdEeFfLlTtXx][Ss]?[-+]?[0-9.]*)?(_[a-zA-Z0-9][a-zA-Z0-9_]*)?" + "|//|\\*\\*|::|[/<>=]=" + "|[^[:space:]]|[\x80-\xff]+"), PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"), PATTERNS("java", @@ -101,6 +117,7 @@ PATTERNS("csharp", { "default", NULL, -1, { NULL, 0 } }, }; #undef PATTERNS +#undef IPATTERN static struct userdiff_driver driver_true = { "diff=true", |