From: Junio C Hamano Date: Fri, 29 May 2009 05:50:23 +0000 (-0700) Subject: Merge branch 'maint' X-Git-Tag: v1.6.4-rc0~114 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/a9d29038a7acc0d37f801a2a923826a3539f4ae6?hp=6a01554e6350123c78de805d820e90f1c56e5fdc Merge branch 'maint' * maint: fix segfault showing an empty remote --- diff --git a/Documentation/RelNotes-1.6.4.txt b/Documentation/RelNotes-1.6.4.txt new file mode 100644 index 0000000000..af68297af5 --- /dev/null +++ b/Documentation/RelNotes-1.6.4.txt @@ -0,0 +1,93 @@ +GIT v1.6.4 Release Notes +======================== + +With the next major release, "git push" into a branch that is +currently checked out will be refused by default. You can choose +what should happen upon such a push by setting the configuration +variable receive.denyCurrentBranch in the receiving repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://git.or.cz/gitwiki/GitFaq#non-bare + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the reason why this change is needed and the +transition plan. + +For a similar reason, "git push $there :$killed" to delete the branch +$killed in a remote repository $there, if $killed branch is the current +branch pointed at by its HEAD, gets a large warning. You can choose what +should happen upon such a push by setting the configuration variable +receive.denyDeleteCurrent in the receiving repository. + +When the user does not tell "git push" what to push, it has always +pushed matching refs. For some people it is unexpected, and a new +configuration variable push.default has been introduced to allow +changing a different default behaviour. To advertise the new feature, +a big warning is issued if this is not configured and a git push without +arguments is attempted. + + +Updates since v1.6.3 +-------------------- + +(subsystems) + + * gitweb Perl style clean-up. + + * git-svn updates, including a new --authors-prog option to map author + names by invoking an external program. + +(portability) + + * We feed iconv with "UTF-8" instead of "utf8"; the former is + understood more widely. + +(performance) + +(usability, bells and whistles) + + * "git add --edit" lets users edit the whole patch text to fine-tune what + is added to the index. + + * "git log --graph" draws graphs more compactly by using horizonal lines + when able. + + * "git log --decorate" shows shorter refnames by stripping well-known + refs/* prefix. + + * "git send-email" understands quoted aliases in .mailrc files (might + have to be backported to 1.6.3.X). + + * "git send-email" can fetch the sender address from the configuration + variable "sendmail.from" (and "sendmail..from"). + + * "git show-branch" can color its output. + + * "add" and "update" subcommands to "git submodule" learned --reference + option to use local clone with references. + +(developers) + + * A major part of the "git bisect" wrapper has moved to C. + +Fixes since v1.6.3 +------------------ + +All of the fixes in v1.6.3.X maintenance series are included in this +release, unless otherwise noted. + +Here are fixes that this release has, but have not been backported to +v1.6.3.X series. + + * The way Git.pm sets up a Repository object was not friendly to callers + that chdir around. It now internally records the repository location + as an absolute path when autodetected. + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.6.3.1-168-g23807fa +git shortlog --no-merges $O..master ^maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 5dcad94f84..2c031620c5 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -604,6 +604,12 @@ color.pager:: A boolean to enable/disable colored output when the pager is in use (default is true). +color.showbranch:: + A boolean to enable/disable color in the output of + linkgit:git-show-branch[1]. May be set to `always`, + `false` (or `never`) or `auto` (or `true`), in which case colors are used + only when the output is to a terminal. Defaults to false. + color.status:: A boolean to enable/disable color in the output of linkgit:git-status[1]. May be set to `always`, diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index d938b42289..ab1943c712 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p] - [--all | [--update | -u]] [--intent-to-add | -N] + [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N] [--refresh] [--ignore-errors] [--] ... DESCRIPTION @@ -76,6 +76,15 @@ OPTIONS bypassed and the 'patch' subcommand is invoked using each of the specified filepatterns before exiting. +-e, \--edit:: + Open the diff vs. the index in an editor and let the user + edit it. After the editor was closed, adjust the hunk headers + and apply the patch to the index. ++ +*NOTE*: Obviously, if you change anything else than the first character +on lines beginning with a space or a minus, the patch will no longer +apply. + -u:: --update:: Update only files that git already knows about, staging modified diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index cbd4275871..ae201deb7a 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -111,6 +111,7 @@ OPTIONS --no-abbrev:: Display the full sha1s in the output listing rather than abbreviating them. +-t:: --track:: When creating a new branch, set up configuration to mark the start-point branch as "upstream" from the new branch. This diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index c1ce26884e..0b7982ea76 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -25,6 +25,10 @@ imposes the following rules on how references are named: grouping, but no slash-separated component can begin with a dot `.`. +. They must contain at least one `/`. This enforces the presence of a + category like `heads/`, `tags/` etc. but the actual names are not + restricted. + . They cannot have two consecutive dots `..` anywhere. . They cannot have ASCII control characters (i.e. bytes whose @@ -38,6 +42,8 @@ imposes the following rules on how references are named: . They cannot contain a sequence `@{`. +- They cannot contain a `\\`. + These rules make it easy for shell script based tools to parse reference names, pathname expansion by the shell when a reference name is used unquoted (by mistake), and also avoids ambiguities in certain diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 3d5a066c31..26f3b7b2b0 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -231,8 +231,7 @@ OPTIONS -s :: --strategy=:: - Use the given merge strategy; can be supplied more than - once to specify them in the order they should be tried. + Use the given merge strategy. If there is no `-s` option, a built-in list of strategies is used instead ('git-merge-recursive' when merging a single head, 'git-merge-octopus' otherwise). This implies --merge. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 52c353e674..4bbdd056da 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -30,6 +30,11 @@ OPTIONS Only meaningful in `--parseopt` mode. Tells the option parser to echo out the first `--` met instead of skipping it. +--sq-quote:: + Use 'git-rev-parse' in shell quoting mode (see SQ-QUOTE + section below). In contrast to the `--sq` option below, this + mode does only quoting. Nothing else is done to command input. + --revs-only:: Do not output flags and parameters not meant for 'git-rev-list' command. @@ -64,7 +69,8 @@ OPTIONS properly quoted for consumption by shell. Useful when you expect your parameter to contain whitespaces and newlines (e.g. when using pickaxe `-S` with - 'git-diff-\*'). + 'git-diff-\*'). In contrast to the `--sq-quote` option, + the command input is still interpreted as usual. --not:: When showing object names, prefix them with '{caret}' and @@ -406,6 +412,33 @@ C? option C with an optional argument" eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?` ------------ +SQ-QUOTE +-------- + +In `--sq-quote` mode, 'git-rev-parse' echoes on the standard output a +single line suitable for `sh(1)` `eval`. This line is made by +normalizing the arguments following `--sq-quote`. Nothing other than +quoting the arguments is done. + +If you want command input to still be interpreted as usual by +'git-rev-parse' before the output is shell quoted, see the `--sq` +option. + +Example +~~~~~~~ + +------------ +$ cat >your-git-script.sh <<\EOF +#!/bin/sh +args=$(git rev-parse --sq-quote "$@") # quote user-supplied arguments +command="git frotz -n24 $args" # and use it inside a handcrafted + # command line +eval "$command" +EOF + +$ sh your-git-script.sh "a b'c" +------------ + EXAMPLES -------- diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 794224b1b3..f940770279 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -69,9 +69,12 @@ and In-Reply-To headers will be used unless they are removed. Missing From or In-Reply-To headers will be prompted for. --from=
:: - Specify the sender of the emails. This will default to - the value GIT_COMMITTER_IDENT, as returned by "git var -l". - The user will still be prompted to confirm this entry. + Specify the sender of the emails. If not specified on the command line, + the value of the 'sendemail.from' configuration option is used. If + neither the command line option nor 'sendemail.from' are set, then the + user will be prompted for the value. The default for the prompt will be + the value of GIT_AUTHOR_IDENT, or GIT_COMMITTER_IDENT if that is not + set, as returned by "git var -l". --in-reply-to=:: Specify the contents of the first In-Reply-To header. diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 51a4e9d6d7..edd8f6463e 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git show-branch' [--all] [--remotes] [--topo-order] [--current] [--more= | --list | --independent | --merge-base] + [--color | --no-color] [--no-name | --sha1-name] [--topics] [ | ]... 'git show-branch' (-g|--reflog)[=[,]] [--list] [] @@ -107,6 +108,14 @@ OPTIONS When no explicit parameter is given, it defaults to the current branch (or `HEAD` if it is detached). +--color:: + Color the status sign (one of these: `*` `!` `+` `-`) of each commit + corresponding to the branch it's in. + +--no-color:: + Turn off colored output, even when the configuration file gives the + default to color output. + Note that --more, --list, --independent and --merge-base options are mutually exclusive. diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 3b8df44673..14256c695b 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,10 +9,12 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b branch] [--] +'git submodule' [--quiet] add [-b branch] + [--reference ] [--] 'git submodule' [--quiet] status [--cached] [--] [...] 'git submodule' [--quiet] init [--] [...] -'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [...] +'git submodule' [--quiet] update [--init] [-N|--no-fetch] + [--reference ] [--] [...] 'git submodule' [--quiet] summary [--summary-limit ] [commit] [--] [...] 'git submodule' [--quiet] foreach 'git submodule' [--quiet] sync [--] [...] @@ -177,6 +179,14 @@ OPTIONS This option is only valid for the update command. Don't fetch new objects from the remote site. +--reference :: + This option is only valid for add and update commands. These + commands sometimes need to clone a remote repository. In this case, + this option will be passed to the linkgit:git-clone[1] command. ++ +*NOTE*: Do *not* use this option unless you have read the note +for linkgit:git-clone[1]'s --reference and --shared options carefully. + ...:: Paths to submodule(s). When specified this will restrict the command to only operate on the submodules found at the specified paths. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 1c40894669..ca3fc3de1f 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -398,6 +398,14 @@ after the authors-file is modified should continue operation. config key: svn.authorsfile +--authors-prog=:: + +If this option is specified, for each SVN committer name that does not +exist in the authors file, the given file is executed with the committer +name as the first argument. The program is expected to return a single +line of the form "Name ", which will be treated as if included in +the authors file. + -q:: --quiet:: Make 'git-svn' less verbose. Specify a second time to make it diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index e30c602f47..b43458eae6 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -137,6 +137,10 @@ There are some macros to easily define options: Introduce a boolean option. If used, `int_var` is bitwise-ored with `mask`. +`OPT_NEGBIT(short, long, &int_var, description, mask)`:: + Introduce a boolean option. + If used, `int_var` is bitwise-anded with the inverted `mask`. + `OPT_SET_INT(short, long, &int_var, description, integer)`:: Introduce a boolean option. If used, set `int_var` to `integer`. @@ -166,6 +170,14 @@ There are some macros to easily define options: `OPT_ARGUMENT(long, description)`:: Introduce a long-option argument that will be kept in `argv[]`. +`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`:: + Recognize numerical options like -123 and feed the integer as + if it was an argument to the function given by `func_ptr`. + The result will be put into `var`. There can be only one such + option definition. It cannot be negated and it takes no + arguments. Short options that happen to be digits take + precedence over it. + The last element of the array must be `OPT_END()`. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index d292e3a2d3..39cde784c9 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.3.1 +DEF_VER=v1.6.3.GIT LF=' ' diff --git a/Makefile b/Makefile index d21b4eb54f..eaae45db7c 100644 --- a/Makefile +++ b/Makefile @@ -228,6 +228,7 @@ ETC_GITCONFIG = etc/gitconfig endif lib = lib # DESTDIR= +pathsep = : # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl @@ -816,6 +817,7 @@ ifneq (,$(findstring CYGWIN,$(uname_S))) UNRELIABLE_FSTAT = UnfortunatelyYes endif ifneq (,$(findstring MINGW,$(uname_S))) + pathsep = ; NO_PREAD = YesPlease NO_OPENSSL = YesPlease NO_CURL = YesPlease @@ -1252,7 +1254,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ - -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ $@.sh >$@+ && \ @@ -1271,7 +1272,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e ' h' \ - -e ' s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ + -e ' s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ -e ' H' \ -e ' x' \ -e '}' \ diff --git a/RelNotes b/RelNotes index a433be58b7..f8e49a5070 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes-1.6.3.2.txt \ No newline at end of file +Documentation/RelNotes-1.6.4.txt \ No newline at end of file diff --git a/bisect.c b/bisect.c index 58f7e6f773..f57b62cddd 100644 --- a/bisect.c +++ b/bisect.c @@ -6,15 +6,30 @@ #include "list-objects.h" #include "quote.h" #include "sha1-lookup.h" +#include "run-command.h" #include "bisect.h" -static unsigned char (*skipped_sha1)[20]; -static int skipped_sha1_nr; -static int skipped_sha1_alloc; +struct sha1_array { + unsigned char (*sha1)[20]; + int sha1_nr; + int sha1_alloc; + int sorted; +}; + +static struct sha1_array good_revs; +static struct sha1_array skipped_revs; + +static const unsigned char *current_bad_sha1; + +struct argv_array { + const char **argv; + int argv_nr; + int argv_alloc; +}; -static const char **rev_argv; -static int rev_argv_nr; -static int rev_argv_alloc; +static const char *argv_diff_tree[] = {"diff-tree", "--pretty", NULL, NULL}; +static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL}; +static const char *argv_show_branch[] = {"show-branch", NULL, NULL}; /* bits #0-15 in revision.h */ @@ -398,23 +413,37 @@ struct commit_list *find_bisection(struct commit_list *list, return best; } +static void argv_array_push(struct argv_array *array, const char *string) +{ + ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc); + array->argv[array->argv_nr++] = string; +} + +static void argv_array_push_sha1(struct argv_array *array, + const unsigned char *sha1, + const char *format) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, format, sha1_to_hex(sha1)); + argv_array_push(array, strbuf_detach(&buf, NULL)); +} + +static void sha1_array_push(struct sha1_array *array, + const unsigned char *sha1) +{ + ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc); + hashcpy(array->sha1[array->sha1_nr++], sha1); +} + static int register_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { if (!strcmp(refname, "bad")) { - ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); - rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1)); + current_bad_sha1 = sha1; } else if (!prefixcmp(refname, "good-")) { - const char *hex = sha1_to_hex(sha1); - char *good = xmalloc(strlen(hex) + 2); - *good = '^'; - memcpy(good + 1, hex, strlen(hex) + 1); - ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); - rev_argv[rev_argv_nr++] = good; + sha1_array_push(&good_revs, sha1); } else if (!prefixcmp(refname, "skip-")) { - ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1, - skipped_sha1_alloc); - hashcpy(skipped_sha1[skipped_sha1_nr++], sha1); + sha1_array_push(&skipped_revs, sha1); } return 0; @@ -425,7 +454,7 @@ static int read_bisect_refs(void) return for_each_ref_in("refs/bisect/", register_ref, NULL); } -void read_bisect_paths(void) +void read_bisect_paths(struct argv_array *array) { struct strbuf str = STRBUF_INIT; const char *filename = git_path("BISECT_NAMES"); @@ -440,8 +469,8 @@ void read_bisect_paths(void) strbuf_trim(&str); quoted = strbuf_detach(&str, NULL); - res = sq_dequote_to_argv(quoted, &rev_argv, - &rev_argv_nr, &rev_argv_alloc); + res = sq_dequote_to_argv(quoted, &array->argv, + &array->argv_nr, &array->argv_alloc); if (res) die("Badly quoted content in file '%s': %s", filename, quoted); @@ -451,26 +480,45 @@ void read_bisect_paths(void) fclose(fp); } -static int skipcmp(const void *a, const void *b) +static int array_cmp(const void *a, const void *b) { return hashcmp(a, b); } -static void prepare_skipped(void) +static void sort_sha1_array(struct sha1_array *array) +{ + qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp); + + array->sorted = 1; +} + +static const unsigned char *sha1_access(size_t index, void *table) { - qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp); + unsigned char (*array)[20] = table; + return array[index]; } -static const unsigned char *skipped_sha1_access(size_t index, void *table) +static int lookup_sha1_array(struct sha1_array *array, + const unsigned char *sha1) { - unsigned char (*skipped)[20] = table; - return skipped[index]; + if (!array->sorted) + sort_sha1_array(array); + + return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access); } -static int lookup_skipped(unsigned char *sha1) +static char *join_sha1_array_hex(struct sha1_array *array, char delim) { - return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr, - skipped_sha1_access); + struct strbuf joined_hexs = STRBUF_INIT; + int i; + + for (i = 0; i < array->sha1_nr; i++) { + strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i])); + if (i + 1 < array->sha1_nr) + strbuf_addch(&joined_hexs, delim); + } + + return strbuf_detach(&joined_hexs, NULL); } struct commit_list *filter_skipped(struct commit_list *list, @@ -481,15 +529,14 @@ struct commit_list *filter_skipped(struct commit_list *list, *tried = NULL; - if (!skipped_sha1_nr) + if (!skipped_revs.sha1_nr) return list; - prepare_skipped(); - while (list) { struct commit_list *next = list->next; list->next = NULL; - if (0 <= lookup_skipped(list->item->object.sha1)) { + if (0 <= lookup_sha1_array(&skipped_revs, + list->item->object.sha1)) { /* Move current to tried list */ *tried = list; tried = &list->next; @@ -508,49 +555,328 @@ struct commit_list *filter_skipped(struct commit_list *list, static void bisect_rev_setup(struct rev_info *revs, const char *prefix) { + struct argv_array rev_argv = { NULL, 0, 0 }; + int i; + init_revisions(revs, prefix); revs->abbrev = 0; revs->commit_format = CMIT_FMT_UNSPECIFIED; - /* argv[0] will be ignored by setup_revisions */ - ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); - rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup"); + /* rev_argv.argv[0] will be ignored by setup_revisions */ + argv_array_push(&rev_argv, xstrdup("bisect_rev_setup")); + argv_array_push_sha1(&rev_argv, current_bad_sha1, "%s"); + for (i = 0; i < good_revs.sha1_nr; i++) + argv_array_push_sha1(&rev_argv, good_revs.sha1[i], "^%s"); + argv_array_push(&rev_argv, xstrdup("--")); + read_bisect_paths(&rev_argv); + argv_array_push(&rev_argv, NULL); - if (read_bisect_refs()) - die("reading bisect refs failed"); + setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL); + revs->limited = 1; +} - ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); - rev_argv[rev_argv_nr++] = xstrdup("--"); +static void bisect_common(struct rev_info *revs, int *reaches, int *all) +{ + if (prepare_revision_walk(revs)) + die("revision walk setup failed"); + if (revs->tree_objects) + mark_edges_uninteresting(revs->commits, revs, NULL); - read_bisect_paths(); + revs->commits = find_bisection(revs->commits, reaches, all, + !!skipped_revs.sha1_nr); +} - ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc); - rev_argv[rev_argv_nr++] = NULL; +static void exit_if_skipped_commits(struct commit_list *tried, + const unsigned char *bad) +{ + if (!tried) + return; + + printf("There are only 'skip'ped commits left to test.\n" + "The first bad commit could be any of:\n"); + print_commit_list(tried, "%s\n", "%s\n"); + if (bad) + printf("%s\n", sha1_to_hex(bad)); + printf("We cannot bisect more!\n"); + exit(2); +} - setup_revisions(rev_argv_nr, rev_argv, revs, NULL); +static int is_expected_rev(const unsigned char *sha1) +{ + const char *filename = git_path("BISECT_EXPECTED_REV"); + struct stat st; + struct strbuf str = STRBUF_INIT; + FILE *fp; + int res = 0; - revs->limited = 1; + if (stat(filename, &st) || !S_ISREG(st.st_mode)) + return 0; + + fp = fopen(filename, "r"); + if (!fp) + return 0; + + if (strbuf_getline(&str, fp, '\n') != EOF) + res = !strcmp(str.buf, sha1_to_hex(sha1)); + + strbuf_release(&str); + fclose(fp); + + return res; } -int bisect_next_vars(const char *prefix) +static void mark_expected_rev(char *bisect_rev_hex) +{ + int len = strlen(bisect_rev_hex); + const char *filename = git_path("BISECT_EXPECTED_REV"); + int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + + if (fd < 0) + die("could not create file '%s': %s", + filename, strerror(errno)); + + bisect_rev_hex[len] = '\n'; + write_or_die(fd, bisect_rev_hex, len + 1); + bisect_rev_hex[len] = '\0'; + + if (close(fd) < 0) + die("closing file %s: %s", filename, strerror(errno)); +} + +static int bisect_checkout(char *bisect_rev_hex) +{ + int res; + + mark_expected_rev(bisect_rev_hex); + + argv_checkout[2] = bisect_rev_hex; + res = run_command_v_opt(argv_checkout, RUN_GIT_CMD); + if (res) + exit(res); + + argv_show_branch[1] = bisect_rev_hex; + return run_command_v_opt(argv_show_branch, RUN_GIT_CMD); +} + +static struct commit *get_commit_reference(const unsigned char *sha1) +{ + struct commit *r = lookup_commit_reference(sha1); + if (!r) + die("Not a valid commit name %s", sha1_to_hex(sha1)); + return r; +} + +static struct commit **get_bad_and_good_commits(int *rev_nr) +{ + int len = 1 + good_revs.sha1_nr; + struct commit **rev = xmalloc(len * sizeof(*rev)); + int i, n = 0; + + rev[n++] = get_commit_reference(current_bad_sha1); + for (i = 0; i < good_revs.sha1_nr; i++) + rev[n++] = get_commit_reference(good_revs.sha1[i]); + *rev_nr = n; + + return rev; +} + +static void handle_bad_merge_base(void) +{ + if (is_expected_rev(current_bad_sha1)) { + char *bad_hex = sha1_to_hex(current_bad_sha1); + char *good_hex = join_sha1_array_hex(&good_revs, ' '); + + fprintf(stderr, "The merge base %s is bad.\n" + "This means the bug has been fixed " + "between %s and [%s].\n", + bad_hex, bad_hex, good_hex); + + exit(3); + } + + fprintf(stderr, "Some good revs are not ancestor of the bad rev.\n" + "git bisect cannot work properly in this case.\n" + "Maybe you mistake good and bad revs?\n"); + exit(1); +} + +void handle_skipped_merge_base(const unsigned char *mb) +{ + char *mb_hex = sha1_to_hex(mb); + char *bad_hex = sha1_to_hex(current_bad_sha1); + char *good_hex = join_sha1_array_hex(&good_revs, ' '); + + fprintf(stderr, "Warning: the merge base between %s and [%s] " + "must be skipped.\n" + "So we cannot be sure the first bad commit is " + "between %s and %s.\n" + "We continue anyway.\n", + bad_hex, good_hex, mb_hex, bad_hex); + free(good_hex); +} + +/* + * "check_merge_bases" checks that merge bases are not "bad". + * + * - If one is "bad", it means the user assumed something wrong + * and we must exit with a non 0 error code. + * - If one is "good", that's good, we have nothing to do. + * - If one is "skipped", we can't know but we should warn. + * - If we don't know, we should check it out and ask the user to test. + */ +static void check_merge_bases(void) +{ + struct commit_list *result; + int rev_nr; + struct commit **rev = get_bad_and_good_commits(&rev_nr); + + result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0); + + for (; result; result = result->next) { + const unsigned char *mb = result->item->object.sha1; + if (!hashcmp(mb, current_bad_sha1)) { + handle_bad_merge_base(); + } else if (0 <= lookup_sha1_array(&good_revs, mb)) { + continue; + } else if (0 <= lookup_sha1_array(&skipped_revs, mb)) { + handle_skipped_merge_base(mb); + } else { + printf("Bisecting: a merge base must be tested\n"); + exit(bisect_checkout(sha1_to_hex(mb))); + } + } + + free(rev); + free_commit_list(result); +} + +/* + * This function runs the command "git rev-list $_good ^$_bad" + * and returns 1 if it produces some output, 0 otherwise. + */ +static int check_ancestors(void) +{ + struct argv_array rev_argv = { NULL, 0, 0 }; + struct strbuf str = STRBUF_INIT; + int i, result = 0; + struct child_process rls; + FILE *rls_fout; + + argv_array_push(&rev_argv, xstrdup("rev-list")); + argv_array_push_sha1(&rev_argv, current_bad_sha1, "^%s"); + for (i = 0; i < good_revs.sha1_nr; i++) + argv_array_push_sha1(&rev_argv, good_revs.sha1[i], "%s"); + argv_array_push(&rev_argv, NULL); + + memset(&rls, 0, sizeof(rls)); + rls.argv = rev_argv.argv; + rls.out = -1; + rls.git_cmd = 1; + if (start_command(&rls)) + die("Could not launch 'git rev-list' command."); + rls_fout = fdopen(rls.out, "r"); + while (strbuf_getline(&str, rls_fout, '\n') != EOF) { + strbuf_trim(&str); + if (*str.buf) { + result = 1; + break; + } + } + fclose(rls_fout); + finish_command(&rls); + + return result; +} + +/* + * "check_good_are_ancestors_of_bad" checks that all "good" revs are + * ancestor of the "bad" rev. + * + * If that's not the case, we need to check the merge bases. + * If a merge base must be tested by the user, its source code will be + * checked out to be tested by the user and we will exit. + */ +static void check_good_are_ancestors_of_bad(const char *prefix) +{ + const char *filename = git_path("BISECT_ANCESTORS_OK"); + struct stat st; + int fd; + + if (!current_bad_sha1) + die("a bad revision is needed"); + + /* Check if file BISECT_ANCESTORS_OK exists. */ + if (!stat(filename, &st) && S_ISREG(st.st_mode)) + return; + + /* Bisecting with no good rev is ok. */ + if (good_revs.sha1_nr == 0) + return; + + if (check_ancestors()) + check_merge_bases(); + + /* Create file BISECT_ANCESTORS_OK. */ + fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd < 0) + warning("could not create file '%s': %s", + filename, strerror(errno)); + else + close(fd); +} + +/* + * We use the convention that exiting with an exit code 10 means that + * the bisection process finished successfully. + * In this case the calling shell script should exit 0. + */ +int bisect_next_all(const char *prefix) { struct rev_info revs; - struct rev_list_info info; - int reaches = 0, all = 0; + struct commit_list *tried; + int reaches = 0, all = 0, nr; + const unsigned char *bisect_rev; + char bisect_rev_hex[41]; + + if (read_bisect_refs()) + die("reading bisect refs failed"); - memset(&info, 0, sizeof(info)); - info.revs = &revs; - info.bisect_show_flags = BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED; + check_good_are_ancestors_of_bad(prefix); bisect_rev_setup(&revs, prefix); - if (prepare_revision_walk(&revs)) - die("revision walk setup failed"); - if (revs.tree_objects) - mark_edges_uninteresting(revs.commits, &revs, NULL); + bisect_common(&revs, &reaches, &all); + + revs.commits = filter_skipped(revs.commits, &tried, 0); - revs.commits = find_bisection(revs.commits, &reaches, &all, - !!skipped_sha1_nr); + if (!revs.commits) { + /* + * We should exit here only if the "bad" + * commit is also a "skip" commit. + */ + exit_if_skipped_commits(tried, NULL); - return show_bisect_vars(&info, reaches, all); + printf("%s was both good and bad\n", + sha1_to_hex(current_bad_sha1)); + exit(1); + } + + bisect_rev = revs.commits->item->object.sha1; + memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), 41); + + if (!hashcmp(bisect_rev, current_bad_sha1)) { + exit_if_skipped_commits(tried, current_bad_sha1); + printf("%s is first bad commit\n", bisect_rev_hex); + argv_diff_tree[2] = bisect_rev_hex; + run_command_v_opt(argv_diff_tree, RUN_GIT_CMD); + /* This means the bisection process succeeded. */ + exit(10); + } + + nr = all - reaches - 1; + printf("Bisecting: %d revisions left to test after this " + "(roughly %d steps)\n", nr, estimate_bisect_steps(all)); + + return bisect_checkout(bisect_rev_hex); } + diff --git a/bisect.h b/bisect.h index fdba913877..fb744fdb79 100644 --- a/bisect.h +++ b/bisect.h @@ -9,10 +9,13 @@ extern struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, int show_all); +extern void print_commit_list(struct commit_list *list, + const char *format_cur, + const char *format_last); + /* bisect_show_flags flags in struct rev_list_info */ #define BISECT_SHOW_ALL (1<<0) #define BISECT_SHOW_TRIED (1<<1) -#define BISECT_SHOW_STRINGED (1<<2) struct rev_list_info { struct rev_info *revs; @@ -24,6 +27,8 @@ struct rev_list_info { extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); -extern int bisect_next_vars(const char *prefix); +extern int bisect_next_all(const char *prefix); + +extern int estimate_bisect_steps(int all); #endif diff --git a/builtin-add.c b/builtin-add.c index ad889aac5b..bee45f00de 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -10,12 +10,14 @@ #include "cache-tree.h" #include "run-command.h" #include "parse-options.h" +#include "diff.h" +#include "revision.h" static const char * const builtin_add_usage[] = { "git add [options] [--] ...", NULL }; -static int patch_interactive, add_interactive; +static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) @@ -187,6 +189,51 @@ int interactive_add(int argc, const char **argv, const char *prefix) return status; } +int edit_patch(int argc, const char **argv, const char *prefix) +{ + char *file = xstrdup(git_path("ADD_EDIT.patch")); + const char *apply_argv[] = { "apply", "--recount", "--cached", + file, NULL }; + struct child_process child; + struct rev_info rev; + int out; + struct stat st; + + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ + + if (read_cache() < 0) + die ("Could not read the index"); + + init_revisions(&rev, prefix); + rev.diffopt.context = 7; + + argc = setup_revisions(argc, argv, &rev, NULL); + rev.diffopt.output_format = DIFF_FORMAT_PATCH; + out = open(file, O_CREAT | O_WRONLY, 0644); + if (out < 0) + die ("Could not open '%s' for writing.", file); + rev.diffopt.file = fdopen(out, "w"); + rev.diffopt.close_file = 1; + if (run_diff_files(&rev, 0)) + die ("Could not write patch"); + + launch_editor(file, NULL, NULL); + + if (stat(file, &st)) + die("Could not stat '%s'", file); + if (!st.st_size) + die("Empty patch. Aborted."); + + memset(&child, 0, sizeof(child)); + child.git_cmd = 1; + child.argv = apply_argv; + if (run_command(&child)) + die ("Could not apply '%s'", file); + + unlink(file); + return 0; +} + static struct lock_file lock_file; static const char ignore_error[] = @@ -201,6 +248,7 @@ static struct option builtin_add_options[] = { OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), + OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), @@ -251,14 +299,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) int require_pathspec; argc = parse_options(argc, argv, builtin_add_options, - builtin_add_usage, 0); + builtin_add_usage, PARSE_OPT_KEEP_ARGV0); if (patch_interactive) add_interactive = 1; if (add_interactive) - exit(interactive_add(argc, argv, prefix)); + exit(interactive_add(argc - 1, argv + 1, prefix)); git_config(add_config, NULL); + if (edit_interactive) + return(edit_patch(argc, argv, prefix)); + argc--; + argv++; + if (addremove && take_worktree_changes) die("-A and -u are mutually incompatible"); if ((addremove || take_worktree_changes) && !argc) { diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c index 8fe778766a..cb3e155116 100644 --- a/builtin-bisect--helper.c +++ b/builtin-bisect--helper.c @@ -4,24 +4,24 @@ #include "bisect.h" static const char * const git_bisect_helper_usage[] = { - "git bisect--helper --next-vars", + "git bisect--helper --next-all", NULL }; int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { - int next_vars = 0; + int next_all = 0; struct option options[] = { - OPT_BOOLEAN(0, "next-vars", &next_vars, - "output next bisect step variables"), + OPT_BOOLEAN(0, "next-all", &next_all, + "perform 'git bisect next'"), OPT_END() }; argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0); - if (!next_vars) + if (!next_all) usage_with_options(git_bisect_helper_usage, options); - /* next-vars */ - return bisect_next_vars(prefix); + /* next-all */ + return bisect_next_all(prefix); } diff --git a/builtin-branch.c b/builtin-branch.c index 91098ca9b1..6aaa708473 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -547,7 +547,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_GROUP("Generic options"), OPT__VERBOSE(&verbose), - OPT_SET_INT( 0 , "track", &track, "set up tracking mode (see git-pull(1))", + OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", BRANCH_TRACK_EXPLICIT), OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", diff --git a/builtin-clone.c b/builtin-clone.c index ba286e0160..c935833c6c 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -104,11 +104,12 @@ static char *get_repo_path(const char *repo, int *is_bundle) static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) { const char *end = repo + strlen(repo), *start; + char *dir; /* - * Strip trailing slashes and /.git + * Strip trailing spaces, slashes and /.git */ - while (repo < end && is_dir_sep(end[-1])) + while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) end--; if (end - repo > 5 && is_dir_sep(end[-5]) && !strncmp(end - 4, ".git", 4)) { @@ -140,10 +141,33 @@ static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) if (is_bare) { struct strbuf result = STRBUF_INIT; strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - return strbuf_detach(&result, 0); + dir = strbuf_detach(&result, 0); + } else + dir = xstrndup(start, end - start); + /* + * Replace sequences of 'control' characters and whitespace + * with one ascii space, remove leading and trailing spaces. + */ + if (*dir) { + char *out = dir; + int prev_space = 1 /* strip leading whitespace */; + for (end = dir; *end; ++end) { + char ch = *end; + if ((unsigned char)ch < '\x20') + ch = '\x20'; + if (isspace(ch)) { + if (prev_space) + continue; + prev_space = 1; + } else + prev_space = 0; + *out++ = ch; + } + *out = '\0'; + if (out > dir && prev_space) + out[-1] = '\0'; } - - return xstrndup(start, end - start); + return dir; } static void strip_trailing_slashes(char *dir) diff --git a/builtin-fetch.c b/builtin-fetch.c index 1f7a3f1ce6..1eec64e9c4 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -202,7 +202,7 @@ static int update_local_ref(struct ref *ref, struct commit *current = NULL, *updated; enum object_type type; struct branch *current_branch = branch_get(NULL); - const char *pretty_ref = prettify_ref(ref); + const char *pretty_ref = prettify_refname(ref->name); *display = 0; type = sha1_object_info(ref->new_sha1, NULL); @@ -294,7 +294,7 @@ static int update_local_ref(struct ref *ref, } } -static int store_updated_refs(const char *url, const char *remote_name, +static int store_updated_refs(const char *raw_url, const char *remote_name, struct ref *ref_map) { FILE *fp; @@ -303,11 +303,13 @@ static int store_updated_refs(const char *url, const char *remote_name, char note[1024]; const char *what, *kind; struct ref *rm; - char *filename = git_path("FETCH_HEAD"); + char *url, *filename = git_path("FETCH_HEAD"); fp = fopen(filename, "a"); if (!fp) return error("cannot open %s: %s\n", filename, strerror(errno)); + + url = transport_anonymize_url(raw_url); for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -358,12 +360,18 @@ static int store_updated_refs(const char *url, const char *remote_name, kind); note_len += sprintf(note + note_len, "'%s' of ", what); } - note_len += sprintf(note + note_len, "%.*s", url_len, url); - fprintf(fp, "%s\t%s\t%s\n", + note[note_len] = '\0'; + fprintf(fp, "%s\t%s\t%s", sha1_to_hex(commit ? commit->object.sha1 : rm->old_sha1), rm->merge ? "" : "not-for-merge", note); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); if (ref) rc |= update_local_ref(ref, what, note); @@ -381,6 +389,7 @@ static int store_updated_refs(const char *url, const char *remote_name, fprintf(stderr, " %s\n", note); } } + free(url); fclose(fp); if (rc & STORE_REF_ERROR_DF_CONFLICT) error("some local refs could not be updated; try running\n" diff --git a/builtin-grep.c b/builtin-grep.c index f88a912ace..5308b346e6 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -10,6 +10,7 @@ #include "tag.h" #include "tree-walk.h" #include "builtin.h" +#include "parse-options.h" #include "grep.h" #ifndef NO_EXTERNAL_GREP @@ -20,7 +21,10 @@ #endif #endif -static int builtin_grep; +static char const * const grep_usage[] = { + "git grep [options] [-e] [...] [[--] path...]", + NULL +}; static int grep_config(const char *var, const char *value, void *cb) { @@ -432,7 +436,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) } #endif -static int grep_cache(struct grep_opt *opt, const char **paths, int cached) +static int grep_cache(struct grep_opt *opt, const char **paths, int cached, + int external_grep_allowed) { int hit = 0; int nr; @@ -444,7 +449,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) * we grep through the checked-out files. It tends to * be a lot more optimized */ - if (!cached && !builtin_grep) { + if (!cached && external_grep_allowed) { hit = external_grep(opt, paths, cached); if (hit >= 0) return hit; @@ -560,25 +565,182 @@ static int grep_object(struct grep_opt *opt, const char **paths, die("unable to grep from object of type %s", typename(obj->type)); } -static const char builtin_grep_usage[] = -"git grep