diff options
53 files changed, 884 insertions, 407 deletions
diff --git a/Documentation/RelNotes/1.8.3.txt b/Documentation/RelNotes/1.8.3.txt index bcbcf15696..5031abfe4a 100644 --- a/Documentation/RelNotes/1.8.3.txt +++ b/Documentation/RelNotes/1.8.3.txt @@ -32,6 +32,18 @@ Updates since v1.8.2 UI, Workflows & Features + * The "--annotate" option to "git send-email" can be turned on (or + off) by default with sendemail.annotate configuration variable (you + can use --no-annotate from the command line to override it). + + * The "--cover-letter" option to "git format-patch" can be turned on + (or off) by default with format.coverLetter configuration + variable. By setting it to 'auto', you can turn it on only for a + series with two or more patches. + + * The bash completion support (in contrib/) learned that cherry-pick + takes a few more options than it already knew about. + * "git help" learned "-g" option to show the list of guides just like list of commands are given with "-a". @@ -67,6 +79,9 @@ UI, Workflows & Features ref by specifying a raw object name from the command line when the server side supports this feature. + * Output from "git log --graph" works better with submodule log + output now. + * "git count-objects -v" learned to report leftover temporary packfiles and other garbage in the object store. @@ -116,6 +131,9 @@ Performance, Internal Implementation, etc. * Updates for building under msvc. + * The stack footprint of some codepaths that access an object from a + pack has been shrunk. + * The logic to coalesce the same lines removed from the parents in the output from "diff -c/--cc" has been updated, but with an O(n^2) complexity, so this might turn out to be undesirable. @@ -150,7 +168,7 @@ Performance, Internal Implementation, etc. conflicts have been applied. -Also contains minor documentation updates and code clean-ups. +Also contains various documentation updates and code clean-ups. Fixes since v1.8.2 @@ -160,6 +178,37 @@ Unless otherwise noted, all the fixes since v1.8.2 in the maintenance track are contained in this release (see release notes to them for details). + * Smart-capable HTTP servers were not restricted via the + GIT_NAMESPACE mechanism when talking with commit-walker clients, + like they do when talking with smart HTTP clients. + (merge 6130f86 jk/http-dumb-namespaces later to maint). + + * "git merge-tree" did not omit a merge result that is identical to + "our" side in certain cases. + (merge aacecc3 jk/merge-tree-added-identically later to maint). + + * Perl scripts like "git-svn" closed (not redirecting to /dev/null) + the standard error stream, which is not a very smart thing to do. + Later open may return file descriptor #2 for unrelated purpose, and + error reporting code may write into them. + (merge a749c0b tr/perl-keep-stderr-open later to maint). + + * "git show-branch" was not prepared to show a very long run of + ancestor operators e.g. foobar^2~2^2^2^2...^2~4 correctly. + (merge aaa07e3 jk/show-branch-strbuf later to maint). + + * "git diff --diff-algorithm algo" is also understood as "git diff + --diff-algorithm=algo". + (merge 0895c6d jk/diff-algo-finishing-touches later to maint). + + * The new core.commentchar configuration was not applied to a few + places. + (merge 89c3bbd rt/commentchar-fmt-merge-msg later to maint). + + * "git bundle" did not like a bundle created using a commit without + any message as its one of the prerequistes. + (merge 5446e33 lf/bundle-with-tip-wo-message later to maint). + * "git log -S/-G" started paying attention to textconv filter, but there was no way to disable this. Make it honor --no-textconv option. diff --git a/Documentation/config.txt b/Documentation/config.txt index 426d02c9a0..29559c88af 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1110,6 +1110,11 @@ format.signoff:: the rights to submit this work under the same open source license. Please see the 'SubmittingPatches' document for further discussion. +format.coverLetter:: + A boolean that controls whether to generate a cover-letter when + format-patch is invoked, but in addition can be set to "auto", to + generate a cover-letter only when there's more than one patch. + filter.<driver>.clean:: The command which is used to convert the content of a worktree file to a blob upon checkin. See linkgit:gitattributes[5] for @@ -1461,6 +1466,14 @@ http.sslCAPath:: with when fetching or pushing over HTTPS. Can be overridden by the 'GIT_SSL_CAPATH' environment variable. +http.sslTry:: + Attempt to use AUTH SSL/TLS and encrypted data transfers + when connecting via regular FTP protocol. This might be needed + if the FTP server requires it for security reasons or you wish + to connect securely whenever remote FTP server supports it. + Default is false since it might trigger certificate verification + errors on misconfigured servers. + http.maxRequests:: How many HTTP requests to launch in parallel. Can be overridden by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5. @@ -2017,6 +2030,7 @@ sendemail.<identity>.*:: sendemail.aliasesfile:: sendemail.aliasfiletype:: +sendemail.annotate:: sendemail.bcc:: sendemail.cc:: sendemail.cccmd:: diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index 77da564134..bfb106cccd 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -147,6 +147,13 @@ OPTIONS Giving these options is an error when used with `--inetd`; use the facility of inet daemon to achieve the same before spawning 'git daemon' if needed. ++ +Like many programs that switch user id, the daemon does not reset +environment variables such as `$HOME` when it runs git programs, +e.g. `upload-pack` and `receive-pack`. When using this option, you +may also want to set and export `HOME` to point at the home +directory of `<user>` before starting the daemon, and make sure any +Git configuration files in that directory are readable by `<user>`. --enable=<service>:: --disable=<service>:: diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 3a62f50eda..39118774af 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -20,7 +20,7 @@ SYNOPSIS [--ignore-if-in-upstream] [--subject-prefix=Subject-Prefix] [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] - [--cover-letter] [--quiet] [--notes[=<ref>]] + [--[no-]cover-letter] [--quiet] [--notes[=<ref>]] [<common diff options>] [ <since> | <revision range> ] @@ -195,7 +195,7 @@ will want to ensure that threading is disabled for `git send-email`. `Cc:`, and custom) headers added so far from config or command line. ---cover-letter:: +--[no-]cover-letter:: In addition to the patches, generate a cover letter file containing the shortlog and the overall diffstat. You can fill in a description in the file before sending it out. @@ -260,6 +260,7 @@ attachments, and sign off patches with configuration variables. cc = <email> attach [ = mime-boundary-string ] signoff = true + coverletter = auto ------------ diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 0cffef8aa5..40a9a9abc1 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -45,8 +45,9 @@ Composing ~~~~~~~~~ --annotate:: - Review and edit each patch you're about to send. See the - CONFIGURATION section for 'sendemail.multiedit'. + Review and edit each patch you're about to send. Default is the value + of 'sendemail.annotate'. See the CONFIGURATION section for + 'sendemail.multiedit'. --bcc=<address>:: Specify a "Bcc:" value for each email. Default is the value of diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index eb7ba84f1f..2478a3963c 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -100,9 +100,22 @@ to point at the new commit. [[def_detached_HEAD]]detached HEAD:: Normally the <<def_HEAD,HEAD>> stores the name of a - <<def_branch,branch>>. However, Git also allows you to <<def_checkout,check out>> - an arbitrary <<def_commit,commit>> that isn't necessarily the tip of any - particular branch. In this case HEAD is said to be "detached". + <<def_branch,branch>>, and commands that operate on the + history HEAD represents operate on the history leading to the + tip of the branch the HEAD points at. However, Git also + allows you to <<def_checkout,check out>> an arbitrary + <<def_commit,commit>> that isn't necessarily the tip of any + particular branch. The HEAD in such a state is called + "detached". ++ +Note that commands that operate on the history of the current branch +(e.g. `git commit` to build a new history on top of it) still work +while the HEAD is detached. They update the HEAD to point at the tip +of the updated history without affecting any branch. Commands that +update or inquire information _about_ the current branch (e.g. `git +branch --set-upstream-to` that sets what remote tracking branch the +current branch integrates with) obviously do not work, as there is no +(real) current branch to ask about in this state. [[def_dircache]]dircache:: You are *waaaaay* behind. See <<def_index,index>>. diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 66db80296f..49a9a7d53f 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -48,6 +48,12 @@ patience;; this when the branches to be merged have diverged wildly. See also linkgit:git-diff[1] `--patience`. +diff-algorithm=[patience|minimal|histogram|myers];; + Tells 'merge-recursive' to use a different diff algorithm, which + can help avoid mismerges that occur due to unimportant matching + lines (such as braces from distinct functions). See also + linkgit:git-diff[1] `--diff-algorithm`. + ignore-space-change;; ignore-all-space;; ignore-space-at-eol;; diff --git a/builtin/checkout.c b/builtin/checkout.c index f8033f446e..eb51872347 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -729,7 +729,7 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) "If you want to keep them by creating a new branch, " "this may be a good time\nto do so with:\n\n" " git branch new_branch_name %s\n\n"), - sha1_to_hex(commit->object.sha1)); + find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV)); } /* diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 265a9253bf..1c04070869 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -287,10 +287,10 @@ static void credit_people(struct strbuf *out, const char *me; if (kind == 'a') { - label = "\n# By "; + label = "By"; me = git_author_info(IDENT_NO_DATE); } else { - label = "\n# Via "; + label = "Via"; me = git_committer_info(IDENT_NO_DATE); } @@ -300,7 +300,7 @@ static void credit_people(struct strbuf *out, (me = skip_prefix(me, them->items->string)) != NULL && skip_prefix(me, " <"))) return; - strbuf_addstr(out, label); + strbuf_addf(out, "\n%c %s ", comment_line_char, label); add_people_count(out, them); } @@ -503,14 +503,18 @@ static void fmt_merge_msg_sigs(struct strbuf *out) } else { if (tag_number == 2) { struct strbuf tagline = STRBUF_INIT; - strbuf_addf(&tagline, "\n# %s\n", - origins.items[first_tag].string); + strbuf_addch(&tagline, '\n'); + strbuf_add_commented_lines(&tagline, + origins.items[first_tag].string, + strlen(origins.items[first_tag].string)); strbuf_insert(&tagbuf, 0, tagline.buf, tagline.len); strbuf_release(&tagline); } - strbuf_addf(&tagbuf, "\n# %s\n", - origins.items[i].string); + strbuf_addch(&tagbuf, '\n'); + strbuf_add_commented_lines(&tagbuf, + origins.items[i].string, + strlen(origins.items[i].string)); fmt_tag_signature(&tagbuf, &sig, buf, len); } strbuf_release(&sig); diff --git a/builtin/help.c b/builtin/help.c index 034c36c254..062957f629 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -419,13 +419,13 @@ static struct { const char *name; const char *help; } common_guides[] = { - { "attributes", "Defining attributes per path" }, - { "glossary", "A Git glossary" }, - { "ignore", "Specifies intentionally untracked files to ignore" }, - { "modules", "Defining submodule properties" }, - { "revisions", "Specifying revisions and ranges for Git" }, - { "tutorial", "A tutorial introduction to Git (for version 1.5.1 or newer)" }, - { "workflows", "An overview of recommended workflows with Git"}, + { "attributes", N_("Defining attributes per path") }, + { "glossary", N_("A Git glossary") }, + { "ignore", N_("Specifies intentionally untracked files to ignore") }, + { "modules", N_("Defining submodule properties") }, + { "revisions", N_("Specifying revisions and ranges for Git") }, + { "tutorial", N_("A tutorial introduction to Git (for version 1.5.1 or newer)") }, + { "workflows", N_("An overview of recommended workflows with Git") }, }; static void list_common_guides_help(void) diff --git a/builtin/log.c b/builtin/log.c index 0f318107e5..ad46f72950 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -100,9 +100,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, int quiet = 0, source = 0, mailmap = 0; const struct option builtin_log_options[] = { - OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), - OPT_BOOLEAN(0, "source", &source, N_("show source")), - OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")), + OPT_BOOL(0, "source", &source, N_("show source")), + OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_END() @@ -622,6 +622,14 @@ static void add_header(const char *value) static int thread; static int do_signoff; static const char *signature = git_version_string; +static int config_cover_letter; + +enum { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; static int git_format_config(const char *var, const char *value, void *cb) { @@ -683,6 +691,14 @@ static int git_format_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "format.signature")) return git_config_string(&signature, var, value); + if (!strcmp(var, "format.coverletter")) { + if (value && !strcasecmp(value, "auto")) { + config_cover_letter = COVER_AUTO; + return 0; + } + config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; + return 0; + } return git_log_config(var, value, cb); } @@ -794,9 +810,37 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name) } } +static char *find_branch_name(struct rev_info *rev) +{ + int i, positive = -1; + unsigned char branch_sha1[20]; + const unsigned char *tip_sha1; + const char *ref; + char *full_ref, *branch = NULL; + + for (i = 0; i < rev->cmdline.nr; i++) { + if (rev->cmdline.rev[i].flags & UNINTERESTING) + continue; + if (positive < 0) + positive = i; + else + return NULL; + } + if (positive < 0) + return NULL; + ref = rev->cmdline.rev[positive].name; + tip_sha1 = rev->cmdline.rev[positive].item->sha1; + if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && + !prefixcmp(full_ref, "refs/heads/") && + !hashcmp(tip_sha1, branch_sha1)) + branch = xstrdup(full_ref + strlen("refs/heads/")); + free(full_ref); + return branch; +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *origin, - int nr, struct commit **list, struct commit *head, + int nr, struct commit **list, const char *branch_name, int quiet) { @@ -810,6 +854,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct diff_options opts; int need_8bit_cte = 0; struct pretty_print_context pp = {0}; + struct commit *head = list[0]; if (rev->commit_format != CMIT_FMT_EMAIL) die(_("Cover letter needs email format")); @@ -827,6 +872,9 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (has_non_ascii(list[i]->buffer)) need_8bit_cte = 1; + if (!branch_name) + branch_name = find_branch_name(rev); + msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode = DATE_RFC2822; @@ -1033,45 +1081,6 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } -static char *find_branch_name(struct rev_info *rev) -{ - int i, positive = -1; - unsigned char branch_sha1[20]; - const unsigned char *tip_sha1; - const char *ref; - char *full_ref, *branch = NULL; - - for (i = 0; i < rev->cmdline.nr; i++) { - if (rev->cmdline.rev[i].flags & UNINTERESTING) - continue; - if (positive < 0) - positive = i; - else - return NULL; - } - if (0 <= positive) { - ref = rev->cmdline.rev[positive].name; - tip_sha1 = rev->cmdline.rev[positive].item->sha1; - } else if (!rev->cmdline.nr && rev->pending.nr == 1 && - !strcmp(rev->pending.objects[0].name, "HEAD")) { - /* - * No actual ref from command line, but "HEAD" from - * rev->def was added in setup_revisions() - * e.g. format-patch --cover-letter -12 - */ - ref = "HEAD"; - tip_sha1 = rev->pending.objects[0].item->sha1; - } else { - return NULL; - } - if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && - !prefixcmp(full_ref, "refs/heads/") && - !hashcmp(tip_sha1, branch_sha1)) - branch = xstrdup(full_ref + strlen("refs/heads/")); - free(full_ref); - return branch; -} - int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1083,10 +1092,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int start_number = -1; int just_numbers = 0; int ignore_if_in_upstream = 0; - int cover_letter = 0; + int cover_letter = -1; int boundary_count = 0; int no_binary_diff = 0; - struct commit *origin = NULL, *head = NULL; + struct commit *origin = NULL; const char *in_reply_to = NULL; struct patch_ids ids; struct strbuf buf = STRBUF_INIT; @@ -1101,12 +1110,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, N_("use [PATCH] even with multiple patches"), PARSE_OPT_NOARG, no_numbered_callback }, - OPT_BOOLEAN('s', "signoff", &do_signoff, N_("add Signed-off-by:")), - OPT_BOOLEAN(0, "stdout", &use_stdout, + OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), - OPT_BOOLEAN(0, "cover-letter", &cover_letter, + OPT_BOOL(0, "cover-letter", &cover_letter, N_("generate a cover letter")), - OPT_BOOLEAN(0, "numbered-files", &just_numbers, + OPT_BOOL(0, "numbered-files", &just_numbers, N_("use simple number sequence for output file names")), OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), N_("use <sfx> instead of '.patch'")), @@ -1280,28 +1289,36 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } if (rev.pending.nr == 1) { + int check_head = 0; + if (rev.max_count < 0 && !rev.show_root_diff) { /* * This is traditional behaviour of "git format-patch * origin" that prepares what the origin side still * does not have. */ - unsigned char sha1[20]; - const char *ref; - rev.pending.objects[0].item->flags |= UNINTERESTING; add_head_to_pending(&rev); - ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); - if (ref && !prefixcmp(ref, "refs/heads/")) - branch_name = xstrdup(ref + strlen("refs/heads/")); - else - branch_name = xstrdup(""); /* no branch */ + check_head = 1; } /* * Otherwise, it is "format-patch -22 HEAD", and/or * "format-patch --root HEAD". The user wants * get_revision() to do the usual traversal. */ + + if (!strcmp(rev.pending.objects[0].name, "HEAD")) + check_head = 1; + + if (check_head) { + unsigned char sha1[20]; + const char *ref; + ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + if (ref && !prefixcmp(ref, "refs/heads/")) + branch_name = xstrdup(ref + strlen("refs/heads/")); + else + branch_name = xstrdup(""); /* no branch */ + } } /* @@ -1310,29 +1327,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) */ rev.show_root_diff = 1; - if (cover_letter) { - /* - * NEEDSWORK:randomly pick one positive commit to show - * diffstat; this is often the tip and the command - * happens to do the right thing in most cases, but a - * complex command like "--cover-letter a b c ^bottom" - * picks "c" and shows diffstat between bottom..c - * which may not match what the series represents at - * all and totally broken. - */ - int i; - for (i = 0; i < rev.pending.nr; i++) { - struct object *o = rev.pending.objects[i].item; - if (!(o->flags & UNINTERESTING)) - head = (struct commit *)o; - } - /* There is nothing to show; it is not an error, though. */ - if (!head) - return 0; - if (!branch_name) - branch_name = find_branch_name(&rev); - } - if (ignore_if_in_upstream) { /* Don't say anything if head and upstream are the same. */ if (rev.pending.nr == 2) { @@ -1364,11 +1358,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) list = xrealloc(list, nr * sizeof(list[0])); list[nr - 1] = commit; } + if (nr == 0) + /* nothing to do */ + return 0; total = nr; if (!keep_subject && auto_number && total > 1) numbered = 1; if (numbered) rev.total = total + start_number - 1; + if (cover_letter == -1) { + if (config_cover_letter == COVER_AUTO) + cover_letter = (total > 1); + else + cover_letter = (config_cover_letter == COVER_ON); + } + if (in_reply_to || thread || cover_letter) rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); if (in_reply_to) { @@ -1381,7 +1385,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, - origin, nr, list, head, branch_name, quiet); + origin, nr, list, branch_name, quiet); total++; start_number--; } diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index bc912e399e..ed25d81b88 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -155,6 +155,11 @@ static int same_entry(struct name_entry *a, struct name_entry *b) a->mode == b->mode; } +static int both_empty(struct name_entry *a, struct name_entry *b) +{ + return !(a->sha1 || b->sha1); +} + static struct merge_list *create_entry(unsigned stage, unsigned mode, const unsigned char *sha1, const char *path) { struct merge_list *res = xcalloc(1, sizeof(*res)); @@ -297,13 +302,10 @@ static void unresolved(const struct traverse_info *info, struct name_entry n[3]) static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *entry, struct traverse_info *info) { /* Same in both? */ - if (same_entry(entry+1, entry+2)) { - if (entry[0].sha1) { - /* Modified identically */ - resolve(info, NULL, entry+1); - return mask; - } - /* "Both added the same" is left unresolved */ + if (same_entry(entry+1, entry+2) || both_empty(entry+0, entry+2)) { + /* Modified, added or removed identically */ + resolve(info, NULL, entry+1); + return mask; } if (same_entry(entry+0, entry+1)) { @@ -319,12 +321,10 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s */ } - if (same_entry(entry+0, entry+2)) { - if (entry[1].sha1 && !S_ISDIR(entry[1].mode)) { - /* We modified, they did not touch -- take ours */ - resolve(info, NULL, entry+1); - return mask; - } + if (same_entry(entry+0, entry+2) || both_empty(entry+0, entry+2)) { + /* We added, modified or removed, they did not touch -- take ours */ + resolve(info, NULL, entry+1); + return mask; } unresolved(info, entry); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index d208fd6c68..90fc6b1b9d 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -162,29 +162,28 @@ static void name_commits(struct commit_list *list, nth = 0; while (parents) { struct commit *p = parents->item; - char newname[1000], *en; + struct strbuf newname = STRBUF_INIT; parents = parents->next; nth++; if (p->util) continue; - en = newname; switch (n->generation) { case 0: - en += sprintf(en, "%s", n->head_name); + strbuf_addstr(&newname, n->head_name); break; case 1: - en += sprintf(en, "%s^", n->head_name); + strbuf_addf(&newname, "%s^", n->head_name); break; default: - en += sprintf(en, "%s~%d", - n->head_name, n->generation); + strbuf_addf(&newname, "%s~%d", + n->head_name, n->generation); break; } if (nth == 1) - en += sprintf(en, "^"); + strbuf_addch(&newname, '^'); else - en += sprintf(en, "^%d", nth); - name_commit(p, xstrdup(newname), 0); + strbuf_addf(&newname, "^%d", nth); + name_commit(p, strbuf_detach(&newname, NULL), 0); i++; name_first_parent_chain(p); } @@ -57,7 +57,7 @@ static int parse_bundle_header(int fd, struct bundle_header *header, * followed by SP and subject line. */ if (get_sha1_hex(buf.buf, sha1) || - (40 <= buf.len && !isspace(buf.buf[40])) || + (buf.len > 40 && !isspace(buf.buf[40])) || (!is_prereq && buf.len <= 40)) { if (report_path) error(_("unrecognized header: %s%s (%d)"), diff --git a/config.mak.uname b/config.mak.uname index 9080054f76..d78fd3df5b 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -507,6 +507,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) compat/win32/dirent.o EXTLIBS += -lws2_32 PTHREAD_LIBS = + NATIVE_CRLF = YesPlease X = .exe SPARSE_FLAGS = -Wno-one-bit-signed-bitfield ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 2ba1461422..6df62c2173 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -53,19 +53,6 @@ __gitdir () fi } -__gitcomp_1 () -{ - local c IFS=$' \t\n' - for c in $1; do - c="$c$2" - case $c in - --*=*|*.) ;; - *) c="$c " ;; - esac - printf '%s\n' "$c" - done -} - # The following function is based on code from: # # bash_completion - programmable completion functions for bash 3.2+ @@ -195,8 +182,18 @@ _get_comp_words_by_ref () } fi -# Generates completion reply with compgen, appending a space to possible -# completion words, if necessary. +__gitcompadd () +{ + local i=0 + for x in $1; do + if [[ "$x" == "$3"* ]]; then + COMPREPLY[i++]="$2$x$4" + fi + done +} + +# Generates completion reply, appending a space to possible completion words, +# if necessary. # It accepts 1 to 4 arguments: # 1: List of possible completion words. # 2: A prefix to be added to each possible completion word (optional). @@ -208,19 +205,25 @@ __gitcomp () case "$cur_" in --*=) - COMPREPLY=() ;; *) - local IFS=$'\n' - COMPREPLY=($(compgen -P "${2-}" \ - -W "$(__gitcomp_1 "${1-}" "${4-}")" \ - -- "$cur_")) + local c i=0 IFS=$' \t\n' + for c in $1; do + c="$c${4-}" + if [[ $c == "$cur_"* ]]; then + case $c in + --*=*|*.) ;; + *) c="$c " ;; + esac + COMPREPLY[i++]="${2-}$c" + fi + done ;; esac } -# Generates completion reply with compgen from newline-separated possible -# completion words by appending a space to all of them. +# Generates completion reply from newline-separated possible completion words +# by appending a space to all of them. # It accepts 1 to 4 arguments: # 1: List of possible completion words, separated by a single newline. # 2: A prefix to be added to each possible completion word (optional). @@ -231,7 +234,7 @@ __gitcomp () __gitcomp_nl () { local IFS=$'\n' - COMPREPLY=($(compgen -P "${2-}" -S "${4- }" -W "$1" -- "${3-$cur}")) + __gitcompadd "$1" "${2-}" "${3-$cur}" "${4- }" } # Generates completion reply with compgen from newline-separated possible @@ -614,7 +617,6 @@ __git_complete_remote_or_refspec () case "$cmd" in push) no_complete_refspec=1 ;; fetch) - COMPREPLY=() return ;; *) ;; @@ -630,7 +632,6 @@ __git_complete_remote_or_refspec () return fi if [ $no_complete_refspec = 1 ]; then - COMPREPLY=() return fi [ "$remote" = "." ] && remote= @@ -951,7 +952,6 @@ _git_am () " return esac - COMPREPLY=() } _git_apply () @@ -971,7 +971,6 @@ _git_apply () " return esac - COMPREPLY=() } _git_add () @@ -1031,7 +1030,6 @@ _git_bisect () __gitcomp_nl "$(__git_refs)" ;; *) - COMPREPLY=() ;; esac } @@ -1124,9 +1122,14 @@ _git_cherry () _git_cherry_pick () { + local dir="$(__gitdir)" + if [ -f "$dir"/CHERRY_PICK_HEAD ]; then + __gitcomp "--continue --quit --abort" + return + fi case "$cur" in --*) - __gitcomp "--edit --no-commit" + __gitcomp "--edit --no-commit --signoff --strategy= --mainline" ;; *) __gitcomp_nl "$(__git_refs)" @@ -1170,7 +1173,6 @@ _git_clone () return ;; esac - COMPREPLY=() } _git_commit () @@ -1354,7 +1356,6 @@ _git_fsck () return ;; esac - COMPREPLY=() } _git_gc () @@ -1365,7 +1366,6 @@ _git_gc () return ;; esac - COMPREPLY=() } _git_gitk () @@ -1442,7 +1442,6 @@ _git_init () return ;; esac - COMPREPLY=() } _git_ls_files () @@ -1578,7 +1577,6 @@ _git_mergetool () return ;; esac - COMPREPLY=() } _git_merge_base () @@ -1831,7 +1829,7 @@ _git_config () local remote="${prev#remote.}" remote="${remote%.fetch}" if [ -z "$cur" ]; then - COMPREPLY=("refs/heads/") + __gitcompadd "refs/heads/" "" "" "" return fi __gitcomp_nl "$(__git_refs_remotes "$remote")" @@ -1891,7 +1889,6 @@ _git_config () return ;; *.*) - COMPREPLY=() return ;; esac @@ -2272,7 +2269,6 @@ _git_remote () __gitcomp "$c" ;; *) - COMPREPLY=() ;; esac } @@ -2388,8 +2384,6 @@ _git_stash () *) if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then __gitcomp "$subcommands" - else - COMPREPLY=() fi ;; esac @@ -2402,14 +2396,12 @@ _git_stash () __gitcomp "--index --quiet" ;; show,--*|drop,--*|branch,--*) - COMPREPLY=() ;; show,*|apply,*|drop,*|pop,*|branch,*) __gitcomp_nl "$(git --git-dir="$(__gitdir)" stash list \ | sed -n -e 's/:.*//p')" ;; *) - COMPREPLY=() ;; esac fi @@ -2526,7 +2518,6 @@ _git_svn () __gitcomp "--revision= --parent" ;; *) - COMPREPLY=() ;; esac fi @@ -2551,13 +2542,10 @@ _git_tag () case "$prev" in -m|-F) - COMPREPLY=() ;; -*|tag) if [ $f = 1 ]; then __gitcomp_nl "$(__git_tags)" - else - COMPREPLY=() fi ;; *) diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr index bd25e08202..aa7bc97bee 100755 --- a/contrib/remote-helpers/git-remote-bzr +++ b/contrib/remote-helpers/git-remote-bzr @@ -249,7 +249,7 @@ def export_files(tree, files): return final def export_branch(branch, name): - global prefix, dirname + global prefix ref = '%s/heads/%s' % (prefix, name) tip = marks.get_tip(name) @@ -335,8 +335,10 @@ def export_branch(branch, name): marks.set_tip(name, revid) def export_tag(repo, name): - global tags - print "reset refs/tags/%s" % name + global tags, prefix + + ref = '%s/tags/%s' % (prefix, name) + print "reset %s" % ref print "from :%u" % rev_to_mark(tags[name]) print @@ -649,6 +651,7 @@ def do_capabilities(parser): print "import" print "export" print "refspec refs/heads/*:%s/heads/*" % prefix + print "refspec refs/tags/*:%s/tags/*" % prefix path = os.path.join(dirname, 'marks-git') diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg index 328c2dc76d..45f6c80d45 100755 --- a/contrib/remote-helpers/git-remote-hg +++ b/contrib/remote-helpers/git-remote-hg @@ -326,6 +326,8 @@ def export_ref(repo, name, kind, head): else: modified, removed = get_filechanges(repo, c, parents[0]) + desc += '\n' + if mode == 'hg': extra_msg = '' @@ -349,7 +351,6 @@ def export_ref(repo, name, kind, head): else: extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value)) - desc += '\n' if extra_msg: desc += '\n--HG--\n' + extra_msg @@ -2255,6 +2255,7 @@ static void builtin_diff(const char *name_a, const char *del = diff_get_color_opt(o, DIFF_FILE_OLD); const char *add = diff_get_color_opt(o, DIFF_FILE_NEW); show_submodule_summary(o->file, one ? one->path : two->path, + line_prefix, one->sha1, two->sha1, two->dirty_submodule, meta, del, add, reset); return; @@ -3596,8 +3597,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts = DIFF_WITH_ALG(options, PATIENCE_DIFF); else if (!strcmp(arg, "--histogram")) options->xdl_opts = DIFF_WITH_ALG(options, HISTOGRAM_DIFF); - else if (!prefixcmp(arg, "--diff-algorithm=")) { - long value = parse_algorithm_value(arg+17); + else if ((argcount = parse_long_opt("diff-algorithm", av, &optarg))) { + long value = parse_algorithm_value(optarg); if (value < 0) return error("option diff-algorithm accepts \"myers\", " "\"minimal\", \"patience\" and \"histogram\""); @@ -3605,6 +3606,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_XDL_CLR(options, NEED_MINIMAL); options->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; options->xdl_opts |= value; + return argcount; } /* flags options */ diff --git a/git-rebase--am.sh b/git-rebase--am.sh index 97f31dc7af..f84854f09a 100644 --- a/git-rebase--am.sh +++ b/git-rebase--am.sh @@ -31,8 +31,8 @@ else rm -f "$GIT_DIR/rebased-patches" git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - --src-prefix=a/ --dst-prefix=b/ \ - --no-renames $root_flag "$revisions" >"$GIT_DIR/rebased-patches" + --src-prefix=a/ --dst-prefix=b/ --no-renames --no-cover-letter \ + $root_flag "$revisions" >"$GIT_DIR/rebased-patches" ret=$? if test 0 != $ret diff --git a/git-send-email.perl b/git-send-email.perl index 7297472818..bd13cc812d 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -54,7 +54,7 @@ git send-email [options] <file | directory | rev-list options > --[no-]bcc <str> * Email Bcc: --subject <str> * Email "Subject:" --in-reply-to <str> * Email "In-Reply-To:" - --annotate * Review each patch that will be sent in an editor. + --[no-]annotate * Review each patch that will be sent in an editor. --compose * Open an editor for introduction. --compose-encoding <str> * Encoding to assume for introduction. --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared @@ -212,7 +212,8 @@ my %config_bool_settings = ( "signedoffbycc" => [\$signed_off_by_cc, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated "validate" => [\$validate, 1], - "multiedit" => [\$multiedit, undef] + "multiedit" => [\$multiedit, undef], + "annotate" => [\$annotate, undef] ); my %config_settings = ( @@ -304,7 +305,7 @@ my $rc = GetOptions("h" => \$help, "smtp-debug:i" => \$debug_net_smtp, "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, - "annotate" => \$annotate, + "annotate!" => \$annotate, "compose" => \$compose, "quiet" => \$quiet, "cc-cmd=s" => \$cc_cmd, @@ -397,6 +397,10 @@ const char *help_unknown_cmd(const char *cmd) int cmd_version(int argc, const char **argv, const char *prefix) { + /* + * The format of this string should be kept stable for compatibility + * with external projects that rely on the output of "git version". + */ printf("git version %s\n", git_version_string); return 0; } diff --git a/http-backend.c b/http-backend.c index 8144f3ad5e..6b85ffac27 100644 --- a/http-backend.c +++ b/http-backend.c @@ -361,17 +361,19 @@ static void run_service(const char **argv) static int show_text_ref(const char *name, const unsigned char *sha1, int flag, void *cb_data) { + const char *name_nons = strip_namespace(name); struct strbuf *buf = cb_data; struct object *o = parse_object(sha1); if (!o) return 0; - strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name); + strbuf_addf(buf, "%s\t%s\n", sha1_to_hex(sha1), name_nons); if (o->type == OBJ_TAG) { o = deref_tag(o, name, 0); if (!o) return 0; - strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), name); + strbuf_addf(buf, "%s\t%s^{}\n", sha1_to_hex(o->sha1), + name_nons); } return 0; } @@ -402,12 +404,40 @@ static void get_info_refs(char *arg) } else { select_getanyfile(); - for_each_ref(show_text_ref, &buf); + for_each_namespaced_ref(show_text_ref, &buf); send_strbuf("text/plain", &buf); } strbuf_release(&buf); } +static int show_head_ref(const char *name, const unsigned char *sha1, + int flag, void *cb_data) +{ + struct strbuf *buf = cb_data; + + if (flag & REF_ISSYMREF) { + unsigned char sha1[20]; + const char *target = resolve_ref_unsafe(name, sha1, 1, NULL); + const char *target_nons = strip_namespace(target); + + strbuf_addf(buf, "ref: %s\n", target_nons); + } else { + strbuf_addf(buf, "%s\n", sha1_to_hex(sha1)); + } + + return 0; +} + +static void get_head(char *arg) +{ + struct strbuf buf = STRBUF_INIT; + + select_getanyfile(); + head_ref_namespaced(show_head_ref, &buf); + send_strbuf("text/plain", &buf); + strbuf_release(&buf); +} + static void get_info_packs(char *arg) { size_t objdirlen = strlen(get_object_directory()); @@ -520,7 +550,7 @@ static struct service_cmd { const char *pattern; void (*imp)(char *); } services[] = { - {"GET", "/HEAD$", get_text_file}, + {"GET", "/HEAD$", get_head}, {"GET", "/info/refs$", get_info_refs}, {"GET", "/objects/info/alternates$", get_text_file}, {"GET", "/objects/info/http-alternates$", get_text_file}, diff --git a/http-push.c b/http-push.c index bd66f6ab6e..395a8cfc10 100644 --- a/http-push.c +++ b/http-push.c @@ -1551,7 +1551,7 @@ static int remote_exists(const char *path) ret = 0; break; case HTTP_ERROR: - http_error(url, HTTP_ERROR); + error("unable to access '%s': %s", url, curl_errorstr); default: ret = -1; } @@ -31,6 +31,7 @@ static CURL *curl_default; char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; +static int curl_ssl_try; static const char *ssl_cert; #if LIBCURL_VERSION_NUM >= 0x070903 static const char *ssl_key; @@ -163,6 +164,10 @@ static int http_options(const char *var, const char *value, void *cb) ssl_cert_password_required = 1; return 0; } + if (!strcmp("http.ssltry", var)) { + curl_ssl_try = git_config_bool(var, value); + return 0; + } if (!strcmp("http.minsessions", var)) { min_curl_sessions = git_config_int(var, value); #ifndef USE_CURL_MULTI @@ -282,7 +287,6 @@ static CURL *get_curl_handle(void) #endif if (ssl_cainfo != NULL) curl_easy_setopt(result, CURLOPT_CAINFO, ssl_cainfo); - curl_easy_setopt(result, CURLOPT_FAILONERROR, 1); if (curl_low_speed_limit > 0 && curl_low_speed_time > 0) { curl_easy_setopt(result, CURLOPT_LOW_SPEED_LIMIT, @@ -307,6 +311,11 @@ static CURL *get_curl_handle(void) if (curl_ftp_no_epsv) curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0); +#ifdef CURLOPT_USE_SSL + if (curl_ssl_try) + curl_easy_setopt(result, CURLOPT_USE_SSL, CURLUSESSL_TRY); +#endif + if (curl_http_proxy) { curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy); curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY); @@ -506,6 +515,7 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, NULL); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 0); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 1); if (http_auth.password) init_curl_http_auth(slot->curl); @@ -761,6 +771,25 @@ char *get_remote_object_url(const char *url, const char *hex, int handle_curl_result(struct slot_results *results) { + /* + * If we see a failing http code with CURLE_OK, we have turned off + * FAILONERROR (to keep the server's custom error response), and should + * translate the code into failure here. + */ + if (results->curl_result == CURLE_OK && + results->http_code >= 400) { + results->curl_result = CURLE_HTTP_RETURNED_ERROR; + /* + * Normally curl will already have put the "reason phrase" + * from the server into curl_errorstr; unfortunately without + * FAILONERROR it is lost, so we can give only the numeric + * status code. + */ + snprintf(curl_errorstr, sizeof(curl_errorstr), + "The requested URL returned error: %ld", + results->http_code); + } + if (results->curl_result == CURLE_OK) { credential_approve(&http_auth); return HTTP_OK; @@ -825,6 +854,8 @@ static int http_request(const char *url, struct strbuf *type, strbuf_addstr(&buf, "Pragma:"); if (options & HTTP_NO_CACHE) strbuf_addstr(&buf, " no-cache"); + if (options & HTTP_KEEP_ERROR) + curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0); headers = curl_slist_append(headers, buf.buf); @@ -836,7 +867,8 @@ static int http_request(const char *url, struct strbuf *type, run_active_slot(slot); ret = handle_curl_result(&results); } else { - error("Unable to start HTTP request for %s", url); + snprintf(curl_errorstr, sizeof(curl_errorstr), + "failed to start HTTP request"); ret = HTTP_START_FAILED; } @@ -862,6 +894,22 @@ static int http_request_reauth(const char *url, int ret = http_request(url, type, result, target, options); if (ret != HTTP_REAUTH) return ret; + + /* + * If we are using KEEP_ERROR, the previous request may have + * put cruft into our output stream; we should clear it out before + * making our next request. We only know how to do this for + * the strbuf case, but that is enough to satisfy current callers. + */ + if (options & HTTP_KEEP_ERROR) { + switch (target) { + case HTTP_REQUEST_STRBUF: + strbuf_reset(result); + break; + default: + die("BUG: HTTP_KEEP_ERROR is only supported with strbufs"); + } + } return http_request(url, type, result, target, options); } @@ -903,15 +951,6 @@ cleanup: return ret; } -int http_error(const char *url, int ret) -{ - /* http_request has already handled HTTP_START_FAILED. */ - if (ret != HTTP_START_FAILED) - error("%s while accessing %s", curl_errorstr, url); - - return ret; -} - int http_fetch_ref(const char *base, struct ref *ref) { char *url; @@ -42,6 +42,15 @@ #define NO_CURL_IOCTL #endif +/* + * CURLOPT_USE_SSL was known as CURLOPT_FTP_SSL up to 7.16.4, + * and the constants were known as CURLFTPSSL_* +*/ +#if !defined(CURLOPT_USE_SSL) && defined(CURLOPT_FTP_SSL) +#define CURLOPT_USE_SSL CURLOPT_FTP_SSL +#define CURLUSESSL_TRY CURLFTPSSL_TRY +#endif + struct slot_results { CURLcode curl_result; long http_code; @@ -118,6 +127,7 @@ extern char *get_remote_object_url(const char *url, const char *hex, /* Options for http_request_*() */ #define HTTP_NO_CACHE 1 +#define HTTP_KEEP_ERROR 2 /* Return values for http_request_*() */ #define HTTP_OK 0 @@ -134,12 +144,6 @@ extern char *get_remote_object_url(const char *url, const char *hex, */ int http_get_strbuf(const char *url, struct strbuf *content_type, struct strbuf *result, int options); -/* - * Prints an error message using error() containing url and curl_errorstr, - * and returns ret. - */ -int http_error(const char *url, int ret); - extern int http_fetch_ref(const char *base, struct ref *ref); /* Helpers for fetching packs */ diff --git a/perl/Git.pm b/perl/Git.pm index 650db90853..dc48159cca 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -1489,12 +1489,12 @@ sub _command_common_pipe { if (not defined $pid) { throw Error::Simple("open failed: $!"); } elsif ($pid == 0) { - if (defined $opts{STDERR}) { - close STDERR; - } if ($opts{STDERR}) { open (STDERR, '>&', $opts{STDERR}) or die "dup failed: $!"; + } elsif (defined $opts{STDERR}) { + open (STDERR, '>', '/dev/null') + or die "opening /dev/null failed: $!"; } _cmd_exec($self, $cmd, @args); } diff --git a/remote-curl.c b/remote-curl.c index 93a09a64c3..60eda63081 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -151,6 +151,33 @@ static void free_discovery(struct discovery *d) } } +static int show_http_message(struct strbuf *type, struct strbuf *msg) +{ + const char *p, *eol; + + /* + * We only show text/plain parts, as other types are likely + * to be ugly to look at on the user's terminal. + * + * TODO should handle "; charset=XXX", and re-encode into + * logoutputencoding + */ + if (strcasecmp(type->buf, "text/plain")) + return -1; + + strbuf_trim(msg); + if (!msg->len) + return -1; + + p = msg->buf; + do { + eol = strchrnul(p, '\n'); + fprintf(stderr, "remote: %.*s\n", (int)(eol - p), p); + p = eol + 1; + } while(*eol); + return 0; +} + static struct discovery* discover_refs(const char *service, int for_push) { struct strbuf exp = STRBUF_INIT; @@ -176,18 +203,20 @@ static struct discovery* discover_refs(const char *service, int for_push) } refs_url = strbuf_detach(&buffer, NULL); - http_ret = http_get_strbuf(refs_url, &type, &buffer, HTTP_NO_CACHE); + http_ret = http_get_strbuf(refs_url, &type, &buffer, + HTTP_NO_CACHE | HTTP_KEEP_ERROR); switch (http_ret) { case HTTP_OK: break; case HTTP_MISSING_TARGET: - die("%s not found: did you run git update-server-info on the" - " server?", refs_url); + show_http_message(&type, &buffer); + die("repository '%s' not found", url); case HTTP_NOAUTH: - die("Authentication failed"); + show_http_message(&type, &buffer); + die("Authentication failed for '%s'", url); default: - http_error(refs_url, http_ret); - die("HTTP request failed"); + show_http_message(&type, &buffer); + die("unable to access '%s': %s", url, curl_errorstr); } last= xcalloc(1, sizeof(*last_discovery)); diff --git a/sha1_file.c b/sha1_file.c index 0ed23981b3..64228a26d0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1648,50 +1648,6 @@ static off_t get_delta_base(struct packed_git *p, return base_offset; } -/* forward declaration for a mutually recursive function */ -static int packed_object_info(struct packed_git *p, off_t offset, - unsigned long *sizep, int *rtype); - -static int packed_delta_info(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos, - enum object_type type, - off_t obj_offset, - unsigned long *sizep) -{ - off_t base_offset; - - base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset); - if (!base_offset) - return OBJ_BAD; - type = packed_object_info(p, base_offset, NULL, NULL); - if (type <= OBJ_NONE) { - struct revindex_entry *revidx; - const unsigned char *base_sha1; - revidx = find_pack_revindex(p, base_offset); - if (!revidx) - return OBJ_BAD; - base_sha1 = nth_packed_object_sha1(p, revidx->nr); - mark_bad_packed_object(p, base_sha1); - type = sha1_object_info(base_sha1, NULL); - if (type <= OBJ_NONE) - return OBJ_BAD; - } - - /* We choose to only get the type of the base object and - * ignore potentially corrupt pack file that expects the delta - * based on a base with a wrong size. This saves tons of - * inflate() calls. - */ - if (sizep) { - *sizep = get_size_from_delta(p, w_curs, curpos); - if (*sizep == 0) - type = OBJ_BAD; - } - - return type; -} - int unpack_object_header(struct packed_git *p, struct pack_window **w_curs, off_t *curpos, @@ -1718,6 +1674,25 @@ int unpack_object_header(struct packed_git *p, return type; } +static int retry_bad_packed_offset(struct packed_git *p, off_t obj_offset) +{ + int type; + struct revindex_entry *revidx; + const unsigned char *sha1; + revidx = find_pack_revindex(p, obj_offset); + if (!revidx) + return OBJ_BAD; + sha1 = nth_packed_object_sha1(p, revidx->nr); + mark_bad_packed_object(p, sha1); + type = sha1_object_info(sha1, NULL); + if (type <= OBJ_NONE) + return OBJ_BAD; + return type; +} + + +#define POI_STACK_PREALLOC 64 + static int packed_object_info(struct packed_git *p, off_t obj_offset, unsigned long *sizep, int *rtype) { @@ -1725,31 +1700,89 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, unsigned long size; off_t curpos = obj_offset; enum object_type type; + off_t small_poi_stack[POI_STACK_PREALLOC]; + off_t *poi_stack = small_poi_stack; + int poi_stack_nr = 0, poi_stack_alloc = POI_STACK_PREALLOC; type = unpack_object_header(p, &w_curs, &curpos, &size); + if (rtype) *rtype = type; /* representation type */ + if (sizep) { + if (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { + off_t tmp_pos = curpos; + off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos, + type, obj_offset); + if (!base_offset) { + type = OBJ_BAD; + goto out; + } + *sizep = get_size_from_delta(p, &w_curs, tmp_pos); + if (*sizep == 0) { + type = OBJ_BAD; + goto out; + } + } else { + *sizep = size; + } + } + + while (type == OBJ_OFS_DELTA || type == OBJ_REF_DELTA) { + off_t base_offset; + /* Push the object we're going to leave behind */ + if (poi_stack_nr >= poi_stack_alloc && poi_stack == small_poi_stack) { + poi_stack_alloc = alloc_nr(poi_stack_nr); + poi_stack = xmalloc(sizeof(off_t)*poi_stack_alloc); + memcpy(poi_stack, small_poi_stack, sizeof(off_t)*poi_stack_nr); + } else { + ALLOC_GROW(poi_stack, poi_stack_nr+1, poi_stack_alloc); + } + poi_stack[poi_stack_nr++] = obj_offset; + /* If parsing the base offset fails, just unwind */ + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + if (!base_offset) + goto unwind; + curpos = obj_offset = base_offset; + type = unpack_object_header(p, &w_curs, &curpos, &size); + if (type <= OBJ_NONE) { + /* If getting the base itself fails, we first + * retry the base, otherwise unwind */ + type = retry_bad_packed_offset(p, base_offset); + if (type > OBJ_NONE) + goto out; + goto unwind; + } + } + switch (type) { - case OBJ_OFS_DELTA: - case OBJ_REF_DELTA: - type = packed_delta_info(p, &w_curs, curpos, - type, obj_offset, sizep); - break; + case OBJ_BAD: case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - if (sizep) - *sizep = size; break; default: error("unknown object type %i at offset %"PRIuMAX" in %s", type, (uintmax_t)obj_offset, p->pack_name); type = OBJ_BAD; } + +out: + if (poi_stack != small_poi_stack) + free(poi_stack); unuse_pack(&w_curs); return type; + +unwind: + while (poi_stack_nr) { + obj_offset = poi_stack[--poi_stack_nr]; + type = retry_bad_packed_offset(p, obj_offset); + if (type > OBJ_NONE) + goto out; + } + type = OBJ_BAD; + goto out; } static void *unpack_compressed_entry(struct packed_git *p, @@ -1811,32 +1844,51 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset) return hash % MAX_DELTA_CACHE; } -static int in_delta_base_cache(struct packed_git *p, off_t base_offset) +static struct delta_base_cache_entry * +get_delta_base_cache_entry(struct packed_git *p, off_t base_offset) { unsigned long hash = pack_entry_hash(p, base_offset); - struct delta_base_cache_entry *ent = delta_base_cache + hash; + return delta_base_cache + hash; +} + +static int eq_delta_base_cache_entry(struct delta_base_cache_entry *ent, + struct packed_git *p, off_t base_offset) +{ return (ent->data && ent->p == p && ent->base_offset == base_offset); } +static int in_delta_base_cache(struct packed_git *p, off_t base_offset) +{ + struct delta_base_cache_entry *ent; + ent = get_delta_base_cache_entry(p, base_offset); + return eq_delta_base_cache_entry(ent, p, base_offset); +} + +static void clear_delta_base_cache_entry(struct delta_base_cache_entry *ent) +{ + ent->data = NULL; + ent->lru.next->prev = ent->lru.prev; + ent->lru.prev->next = ent->lru.next; + delta_base_cached -= ent->size; +} + static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, unsigned long *base_size, enum object_type *type, int keep_cache) { + struct delta_base_cache_entry *ent; void *ret; - unsigned long hash = pack_entry_hash(p, base_offset); - struct delta_base_cache_entry *ent = delta_base_cache + hash; - ret = ent->data; - if (!ret || ent->p != p || ent->base_offset != base_offset) + ent = get_delta_base_cache_entry(p, base_offset); + + if (!eq_delta_base_cache_entry(ent, p, base_offset)) return unpack_entry(p, base_offset, type, base_size); - if (!keep_cache) { - ent->data = NULL; - ent->lru.next->prev = ent->lru.prev; - ent->lru.prev->next = ent->lru.next; - delta_base_cached -= ent->size; - } else { + ret = ent->data; + + if (!keep_cache) + clear_delta_base_cache_entry(ent); + else ret = xmemdupz(ent->data, ent->size); - } *type = ent->type; *base_size = ent->size; return ret; @@ -1900,68 +1952,6 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, static void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); -static void *unpack_delta_entry(struct packed_git *p, - struct pack_window **w_curs, - off_t curpos, - unsigned long delta_size, - off_t obj_offset, - enum object_type *type, - unsigned long *sizep) -{ - void *delta_data, *result, *base; - unsigned long base_size; - off_t base_offset; - - base_offset = get_delta_base(p, w_curs, &curpos, *type, obj_offset); - if (!base_offset) { - error("failed to validate delta base reference " - "at offset %"PRIuMAX" from %s", - (uintmax_t)curpos, p->pack_name); - return NULL; - } - unuse_pack(w_curs); - base = cache_or_unpack_entry(p, base_offset, &base_size, type, 0); - if (!base) { - /* - * We're probably in deep shit, but let's try to fetch - * the required base anyway from another pack or loose. - * This is costly but should happen only in the presence - * of a corrupted pack, and is better than failing outright. - */ - struct revindex_entry *revidx; - const unsigned char *base_sha1; - revidx = find_pack_revindex(p, base_offset); - if (!revidx) - return NULL; - base_sha1 = nth_packed_object_sha1(p, revidx->nr); - error("failed to read delta base object %s" - " at offset %"PRIuMAX" from %s", - sha1_to_hex(base_sha1), (uintmax_t)base_offset, - p->pack_name); - mark_bad_packed_object(p, base_sha1); - base = read_object(base_sha1, type, &base_size); - if (!base) - return NULL; - } - - delta_data = unpack_compressed_entry(p, w_curs, curpos, delta_size); - if (!delta_data) { - error("failed to unpack compressed delta " - "at offset %"PRIuMAX" from %s", - (uintmax_t)curpos, p->pack_name); - free(base); - return NULL; - } - result = patch_delta(base, base_size, - delta_data, delta_size, - sizep); - if (!result) - die("failed to apply delta"); - free(delta_data); - add_delta_base_cache(p, base_offset, base, base_size, *type); - return result; -} - static void write_pack_access_log(struct packed_git *p, off_t obj_offset) { static FILE *log_file; @@ -1982,48 +1972,179 @@ static void write_pack_access_log(struct packed_git *p, off_t obj_offset) int do_check_packed_object_crc; +#define UNPACK_ENTRY_STACK_PREALLOC 64 +struct unpack_entry_stack_ent { + off_t obj_offset; + off_t curpos; + unsigned long size; +}; + void *unpack_entry(struct packed_git *p, off_t obj_offset, - enum object_type *type, unsigned long *sizep) + enum object_type *final_type, unsigned long *final_size) { struct pack_window *w_curs = NULL; off_t curpos = obj_offset; - void *data; + void *data = NULL; + unsigned long size; + enum object_type type; + struct unpack_entry_stack_ent small_delta_stack[UNPACK_ENTRY_STACK_PREALLOC]; + struct unpack_entry_stack_ent *delta_stack = small_delta_stack; + int delta_stack_nr = 0, delta_stack_alloc = UNPACK_ENTRY_STACK_PREALLOC; + int base_from_cache = 0; if (log_pack_access) write_pack_access_log(p, obj_offset); - if (do_check_packed_object_crc && p->index_version > 1) { - struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); - unsigned long len = revidx[1].offset - obj_offset; - if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { - const unsigned char *sha1 = - nth_packed_object_sha1(p, revidx->nr); - error("bad packed object CRC for %s", - sha1_to_hex(sha1)); - mark_bad_packed_object(p, sha1); - unuse_pack(&w_curs); - return NULL; + /* PHASE 1: drill down to the innermost base object */ + for (;;) { + off_t base_offset; + int i; + struct delta_base_cache_entry *ent; + + if (do_check_packed_object_crc && p->index_version > 1) { + struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); + unsigned long len = revidx[1].offset - obj_offset; + if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { + const unsigned char *sha1 = + nth_packed_object_sha1(p, revidx->nr); + error("bad packed object CRC for %s", + sha1_to_hex(sha1)); + mark_bad_packed_object(p, sha1); + unuse_pack(&w_curs); + return NULL; + } + } + + ent = get_delta_base_cache_entry(p, curpos); + if (eq_delta_base_cache_entry(ent, p, curpos)) { + type = ent->type; + data = ent->data; + size = ent->size; + clear_delta_base_cache_entry(ent); + base_from_cache = 1; + break; } + + type = unpack_object_header(p, &w_curs, &curpos, &size); + if (type != OBJ_OFS_DELTA && type != OBJ_REF_DELTA) + break; + + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + if (!base_offset) { + error("failed to validate delta base reference " + "at offset %"PRIuMAX" from %s", + (uintmax_t)curpos, p->pack_name); + /* bail to phase 2, in hopes of recovery */ + data = NULL; + break; + } + + /* push object, proceed to base */ + if (delta_stack_nr >= delta_stack_alloc + && delta_stack == small_delta_stack) { + delta_stack_alloc = alloc_nr(delta_stack_nr); + delta_stack = xmalloc(sizeof(*delta_stack)*delta_stack_alloc); + memcpy(delta_stack, small_delta_stack, + sizeof(*delta_stack)*delta_stack_nr); + } else { + ALLOC_GROW(delta_stack, delta_stack_nr+1, delta_stack_alloc); + } + i = delta_stack_nr++; + delta_stack[i].obj_offset = obj_offset; + delta_stack[i].curpos = curpos; + delta_stack[i].size = size; + + curpos = obj_offset = base_offset; } - *type = unpack_object_header(p, &w_curs, &curpos, sizep); - switch (*type) { + /* PHASE 2: handle the base */ + switch (type) { case OBJ_OFS_DELTA: case OBJ_REF_DELTA: - data = unpack_delta_entry(p, &w_curs, curpos, *sizep, - obj_offset, type, sizep); + if (data) + die("BUG in unpack_entry: left loop at a valid delta"); break; case OBJ_COMMIT: case OBJ_TREE: case OBJ_BLOB: case OBJ_TAG: - data = unpack_compressed_entry(p, &w_curs, curpos, *sizep); + if (!base_from_cache) + data = unpack_compressed_entry(p, &w_curs, curpos, size); break; default: data = NULL; error("unknown object type %i at offset %"PRIuMAX" in %s", - *type, (uintmax_t)obj_offset, p->pack_name); + type, (uintmax_t)obj_offset, p->pack_name); + } + + /* PHASE 3: apply deltas in order */ + + /* invariants: + * 'data' holds the base data, or NULL if there was corruption + */ + while (delta_stack_nr) { + void *delta_data; + void *base = data; + unsigned long delta_size, base_size = size; + int i; + + data = NULL; + + if (base) + add_delta_base_cache(p, obj_offset, base, base_size, type); + + if (!base) { + /* + * We're probably in deep shit, but let's try to fetch + * the required base anyway from another pack or loose. + * This is costly but should happen only in the presence + * of a corrupted pack, and is better than failing outright. + */ + struct revindex_entry *revidx; + const unsigned char *base_sha1; + revidx = find_pack_revindex(p, obj_offset); + if (revidx) { + base_sha1 = nth_packed_object_sha1(p, revidx->nr); + error("failed to read delta base object %s" + " at offset %"PRIuMAX" from %s", + sha1_to_hex(base_sha1), (uintmax_t)obj_offset, + p->pack_name); + mark_bad_packed_object(p, base_sha1); + base = read_object(base_sha1, &type, &base_size); + } + } + + i = --delta_stack_nr; + obj_offset = delta_stack[i].obj_offset; + curpos = delta_stack[i].curpos; + delta_size = delta_stack[i].size; + + if (!base) + continue; + + delta_data = unpack_compressed_entry(p, &w_curs, curpos, delta_size); + + if (!delta_data) { + error("failed to unpack compressed delta " + "at offset %"PRIuMAX" from %s", + (uintmax_t)curpos, p->pack_name); + free(base); + data = NULL; + continue; + } + + data = patch_delta(base, base_size, + delta_data, delta_size, + &size); + if (!data) + die("failed to apply delta"); + + free (delta_data); } + + *final_type = type; + *final_size = size; + unuse_pack(&w_curs); return data; } diff --git a/submodule.c b/submodule.c index 975bc87e48..e728025f60 100644 --- a/submodule.c +++ b/submodule.c @@ -216,6 +216,7 @@ static int prepare_submodule_summary(struct rev_info *rev, const char *path, } static void print_submodule_summary(struct rev_info *rev, FILE *f, + const char *line_prefix, const char *del, const char *add, const char *reset) { static const char format[] = " %m %s"; @@ -226,6 +227,7 @@ static void print_submodule_summary(struct rev_info *rev, FILE *f, struct pretty_print_context ctx = {0}; ctx.date_mode = rev->date_mode; strbuf_setlen(&sb, 0); + strbuf_addstr(&sb, line_prefix); if (commit->object.flags & SYMMETRIC_LEFT) { if (del) strbuf_addstr(&sb, del); @@ -256,6 +258,7 @@ int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg) } void show_submodule_summary(FILE *f, const char *path, + const char *line_prefix, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset) @@ -280,16 +283,18 @@ void show_submodule_summary(FILE *f, const char *path, message = "(revision walker failed)"; if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED) - fprintf(f, "Submodule %s contains untracked content\n", path); + fprintf(f, "%sSubmodule %s contains untracked content\n", + line_prefix, path); if (dirty_submodule & DIRTY_SUBMODULE_MODIFIED) - fprintf(f, "Submodule %s contains modified content\n", path); + fprintf(f, "%sSubmodule %s contains modified content\n", + line_prefix, path); if (!hashcmp(one, two)) { strbuf_release(&sb); return; } - strbuf_addf(&sb, "%sSubmodule %s %s..", meta, path, + strbuf_addf(&sb, "%s%sSubmodule %s %s..", line_prefix, meta, path, find_unique_abbrev(one, DEFAULT_ABBREV)); if (!fast_backward && !fast_forward) strbuf_addch(&sb, '.'); @@ -301,7 +306,7 @@ void show_submodule_summary(FILE *f, const char *path, fwrite(sb.buf, sb.len, 1, f); if (!message) /* only NULL if we succeeded in setting up the walk */ - print_submodule_summary(&rev, f, del, add, reset); + print_submodule_summary(&rev, f, line_prefix, del, add, reset); if (left) clear_commit_marks(left, ~0); if (right) diff --git a/submodule.h b/submodule.h index 3dc1b3fe89..c7ffc7c399 100644 --- a/submodule.h +++ b/submodule.h @@ -19,6 +19,7 @@ int parse_submodule_config_option(const char *var, const char *value); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg); void show_submodule_summary(FILE *f, const char *path, + const char *line_prefix, unsigned char one[20], unsigned char two[20], unsigned dirty_submodule, const char *meta, const char *del, const char *add, const char *reset); diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 938b4cf803..ad8553719a 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -61,6 +61,11 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_COMMITTER_NAME "Custom User" SetEnv GIT_COMMITTER_EMAIL custom@example.com </LocationMatch> +<LocationMatch /smart_namespace/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL + SetEnv GIT_NAMESPACE ns +</LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ <Directory ${GIT_EXEC_PATH}> diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 86ee077107..58d418098d 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1284,4 +1284,37 @@ test_expect_success 'cover letter using branch description (6)' ' grep hello actual >/dev/null ' +test_expect_success 'cover letter with nothing' ' + git format-patch --stdout --cover-letter >actual && + test_line_count = 0 actual +' + +test_expect_success 'cover letter auto' ' + mkdir -p tmp && + test_when_finished "rm -rf tmp; + git config --unset format.coverletter" && + + git config format.coverletter auto && + git format-patch -o tmp -1 >list && + test_line_count = 1 list && + git format-patch -o tmp -2 >list && + test_line_count = 3 list +' + +test_expect_success 'cover letter auto user override' ' + mkdir -p tmp && + test_when_finished "rm -rf tmp; + git config --unset format.coverletter" && + + git config format.coverletter auto && + git format-patch -o tmp --cover-letter -1 >list && + test_line_count = 2 list && + git format-patch -o tmp --cover-letter -2 >list && + test_line_count = 3 list && + git format-patch -o tmp --no-cover-letter -1 >list && + test_line_count = 1 list && + git format-patch -o tmp --no-cover-letter -2 >list && + test_line_count = 2 list +' + test_done diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh index d0b2a457b8..bd43b3d8ef 100755 --- a/t/t4300-merge-tree.sh +++ b/t/t4300-merge-tree.sh @@ -26,8 +26,6 @@ EXPECTED test_expect_success 'file add !A, B' ' cat >expected <<\EXPECTED && -added in local - our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE EXPECTED git reset --hard initial && @@ -38,9 +36,6 @@ EXPECTED test_expect_success 'file add A, B (same)' ' cat >expected <<\EXPECTED && -added in both - our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE - their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE EXPECTED git reset --hard initial && @@ -181,9 +176,6 @@ AAA" && test_expect_success 'file remove A, !B' ' cat >expected <<\EXPECTED && -removed in local - base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE - their 100644 43d5a8ed6ef6c00ff775008633f95787d088285d ONE EXPECTED git reset --hard initial && @@ -283,8 +275,6 @@ test_expect_success 'turn tree to file' ' test_commit "make-file" "dir" "CCC" && git merge-tree add-tree add-another-tree make-file >actual && cat >expect <<-\EOF && - added in local - our 100644 ba629238ca89489f2b350e196ca445e09d8bb834 dir/another removed in remote base 100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path our 100644 43d5a8ed6ef6c00ff775008633f95787d088285d dir/path diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index cdb7d7a7f9..bfdb56a069 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -28,7 +28,7 @@ check_dir() { } test_expect_success 'tar archive of empty tree is empty' ' - git archive --format=tar HEAD >empty.tar && + git archive --format=tar HEAD: >empty.tar && make_dir extract && "$TAR" xf empty.tar -C extract && check_dir extract diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh index c24003565d..2b8c0bac7d 100755 --- a/t/t5404-tracking-branches.sh +++ b/t/t5404-tracking-branches.sh @@ -36,7 +36,7 @@ test_expect_success 'prepare pushable branches' ' ' test_expect_success 'mixed-success push returns error' ' - test_must_fail git push + test_must_fail git push origin : ' test_expect_success 'check tracking branches updated correctly after push' ' diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index ccc55ebf4b..6579a86724 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -345,7 +345,7 @@ test_expect_success 'fetch mirrors do not act as mirrors during push' ' ) && (cd mirror-fetch/child && git branch -m renamed renamed2 && - git push parent + git push parent : ) && (cd mirror-fetch/parent && git rev-parse --verify renamed && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 838e71dafe..4691d51b8c 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -252,7 +252,7 @@ test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf test_expect_success 'push with matching heads' ' mk_test testrepo heads/master && - git push testrepo && + git push testrepo : && check_push_result testrepo $the_commit heads/master ' @@ -281,7 +281,7 @@ test_expect_success 'push --force with matching heads' ' mk_test testrepo heads/master && git push testrepo : && git commit --amend -massaged && - git push --force testrepo && + git push --force testrepo : && ! check_push_result testrepo $the_commit heads/master && git reset --hard $the_commit @@ -504,6 +504,7 @@ test_expect_success 'push with remote.pushdefault' ' test_config remote.down.url down_repo && test_config branch.master.remote up && test_config remote.pushdefault down && + test_config push.default matching && git push && check_push_result up_repo $the_first_commit heads/master && check_push_result down_repo $the_commit heads/master @@ -515,7 +516,7 @@ test_expect_success 'push with config remote.*.pushurl' ' git checkout master && test_config remote.there.url test2repo && test_config remote.there.pushurl testrepo && - git push there && + git push there : && check_push_result testrepo $the_commit heads/master ' @@ -528,6 +529,7 @@ test_expect_success 'push with config branch.*.pushremote' ' test_config remote.down.url down_repo && test_config branch.master.remote up && test_config branch.master.pushremote down && + test_config push.default matching && git push && check_push_result up_repo $the_first_commit heads/master && check_push_result side_repo $the_first_commit heads/master && @@ -541,7 +543,7 @@ test_expect_success 'push with dry-run' ' cd testrepo && old_commit=$(git show-ref -s --verify refs/heads/master) ) && - git push --dry-run testrepo && + git push --dry-run testrepo : && check_push_result testrepo $old_commit heads/master ' @@ -1022,7 +1024,7 @@ test_expect_success 'push --porcelain --dry-run rejected' ' test_expect_success 'push --prune' ' mk_test testrepo heads/master heads/second heads/foo heads/bar && - git push --prune testrepo && + git push --prune testrepo : && check_push_result testrepo $the_commit heads/master && check_push_result testrepo $the_first_commit heads/second && ! check_push_result testrepo $the_first_commit heads/foo heads/bar diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index e2ad260508..12a5dfb17e 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -256,7 +256,7 @@ test_expect_success 'remote.foo.mirror=no has no effect' ' git branch keep master && git push --mirror up && git branch -D keep && - git push up + git push up : ) && ( cd mirror && diff --git a/t/t5519-push-alternates.sh b/t/t5519-push-alternates.sh index c00c9b071d..11fcd37700 100755 --- a/t/t5519-push-alternates.sh +++ b/t/t5519-push-alternates.sh @@ -40,7 +40,7 @@ test_expect_success 'alice works and pushes' ' cd alice-work && echo more >file && git commit -a -m second && - git push ../alice-pub + git push ../alice-pub : ) ' @@ -57,7 +57,7 @@ test_expect_success 'bob fetches from alice, works and pushes' ' git pull ../alice-pub master && echo more bob >file && git commit -a -m third && - git push ../bob-pub + git push ../bob-pub : ) && # Check that the second commit by Alice is not sent @@ -86,7 +86,7 @@ test_expect_success 'alice works and pushes again' ' cd alice-work && echo more alice >file && git commit -a -m fourth && - git push ../alice-pub + git push ../alice-pub : ) ' @@ -99,7 +99,7 @@ test_expect_success 'bob works and pushes' ' cd bob-work && echo yet more bob >file && git commit -a -m fifth && - git push ../bob-pub + git push ../bob-pub : ) ' @@ -115,7 +115,7 @@ test_expect_success 'alice works and pushes yet again' ' git commit -a -m sixth.2 && echo more and more alice >>file && git commit -a -m sixth.3 && - git push ../alice-pub + git push ../alice-pub : ) ' @@ -136,7 +136,7 @@ test_expect_success 'bob works and pushes again' ' git hash-object -t commit -w commit && echo even more bob >file && git commit -a -m seventh && - git push ../bob-pub + git push ../bob-pub : ) ' diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 1947c28c64..8c16e045a0 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -16,6 +16,7 @@ test_expect_success setup ' ( cd gar/bage && git init && + git config push.default matching && >junk && git add junk && git commit -m "Initial junk" diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh index 80d20c876b..f7d0f146f0 100755 --- a/t/t5550-http-fetch.sh +++ b/t/t5550-http-fetch.sh @@ -13,6 +13,7 @@ LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'} start_httpd test_expect_success 'setup repository' ' + git config push.default matching && echo content1 >file && git add file && git commit -m one diff --git a/t/t5551-http-fetch.sh b/t/t5551-http-fetch.sh index 47eb76921d..b23efbbfd9 100755 --- a/t/t5551-http-fetch.sh +++ b/t/t5551-http-fetch.sh @@ -13,6 +13,7 @@ LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5551'} start_httpd test_expect_success 'setup repository' ' + git config push.default matching && echo content >file && git add file && git commit -m one @@ -162,6 +163,30 @@ test_expect_success 'invalid Content-Type rejected' ' grep "not valid:" actual ' +test_expect_success 'create namespaced refs' ' + test_commit namespaced && + git push public HEAD:refs/namespaces/ns/refs/heads/master && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ + symbolic-ref refs/namespaces/ns/HEAD refs/namespaces/ns/refs/heads/master +' + +test_expect_success 'smart clone respects namespace' ' + git clone "$HTTPD_URL/smart_namespace/repo.git" ns-smart && + echo namespaced >expect && + git --git-dir=ns-smart/.git log -1 --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'dumb clone via http-backend respects namespace' ' + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \ + config http.getanyfile true && + GIT_SMART_HTTP=0 git clone \ + "$HTTPD_URL/smart_namespace/repo.git" ns-dumb && + echo namespaced >expect && + git --git-dir=ns-dumb/.git log -1 --format=%s >actual && + test_cmp expect actual +' + test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE test_expect_success EXPENSIVE 'create 50,000 tags in the repo' ' diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index a3a4e47e1d..f01edffa3c 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -8,6 +8,7 @@ LIB_GIT_DAEMON_PORT=${LIB_GIT_DAEMON_PORT-5570} start_git_daemon test_expect_success 'setup repository' ' + git config push.default matching && echo content >file && git add file && git commit -m one diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh index 9e43731fe5..a45c31692e 100755 --- a/t/t5704-bundle.sh +++ b/t/t5704-bundle.sh @@ -58,4 +58,14 @@ test_expect_success 'ridiculously long subject in boundary' ' grep "^-[0-9a-f]\\{40\\} " boundary ' +test_expect_success 'prerequisites with an empty commit message' ' + : >file1 && + git add file1 && + test_tick && + git commit --allow-empty-message -m "" && + test_commit file2 && + git bundle create bundle HEAD^.. && + git bundle verify bundle +' + test_done diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index f73eceabfb..e7e945db09 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -175,6 +175,24 @@ test_expect_success 'merge.log=5 shows all 5 commits' ' test_cmp expected actual ' +test_expect_success '--log=5 with custom comment character' ' + cat >expected <<-EOF && + Merge branch ${apos}left${apos} + + / By Another Author (3) and A U Thor (2) + / Via Another Committer + * left: + Left #5 + Left #4 + Left #3 + Common #2 + Common #1 + EOF + + git -c core.commentchar="/" fmt-merge-msg --log=5 <.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 && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 2a0cfaac32..a4ffea0dbe 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -596,14 +596,14 @@ test_expect_success 'submodule add places git-dir in superprojects git-dir recur git log > ../../../expected ) && git commit -m "added subsubmodule" && - git push + git push origin : ) && (cd .git/modules/deeper/submodule/modules/subsubmodule && git log > ../../../../../actual ) && git add deeper/submodule && git commit -m "update submodule" && - git push && + git push origin : && test_cmp actual expected ) ' diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 9502f2438a..043138631b 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -36,6 +36,7 @@ export CVSROOT CVS_SERVER rm -rf "$CVSWORK" "$SERVERDIR" test_expect_success 'setup' ' + git config push.default matching && echo >empty && git add empty && git commit -q -m "First Commit" && diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index 1c5bc84fa7..8c3db76301 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -84,6 +84,7 @@ export CVSROOT CVS_SERVER rm -rf "$CVSWORK" "$SERVERDIR" test_expect_success 'setup' ' + git config push.default matching && echo "Simple text file" >textfile.c && echo "File with embedded NUL: Q <- there" | q_to_nul > binfile.bin && mkdir subdir && diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 0d4e366232..1140767b50 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -45,7 +45,8 @@ is($r->get_color("color.test.slot1", "red"), $ansi_green, "get_color"); # Failure cases for config: # Save and restore STDERR; we will probably extract this into a # "dies_ok" method and possibly move the STDERR handling to Git.pm. -open our $tmpstderr, ">&STDERR" or die "cannot save STDERR"; close STDERR; +open our $tmpstderr, ">&STDERR" or die "cannot save STDERR"; +open STDERR, ">", "/dev/null" or die "cannot redirect STDERR to /dev/null"; is($r->config("test.dupstring"), "value2", "config: multivar"); eval { $r->config_bool("test.boolother") }; ok($@, "config_bool: non-boolean values fail"); diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index adc1372b3c..6d9d1418a0 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -69,6 +69,7 @@ run_completion () local -a COMPREPLY _words local _cword _words=( $1 ) + test "${1: -1}" == ' ' && _words+=('') (( _cword = ${#_words[@]} - 1 )) __git_wrap__git_main && print_comp } @@ -104,6 +105,23 @@ test_gitcomp () test_cmp expected out } +# Test __gitcomp_nl +# Arguments are: +# 1: current word (cur) +# -: the rest are passed to __gitcomp_nl +test_gitcomp_nl () +{ + local -a COMPREPLY && + sed -e 's/Z$//' >expected && + cur="$1" && + shift && + __gitcomp_nl "$@" && + print_comp && + test_cmp expected out +} + +invalid_variable_name='${foo.bar}' + test_expect_success '__gitcomp - trailing space - options' ' test_gitcomp "--re" "--dry-run --reuse-message= --reedit-message= --reset-author" <<-EOF @@ -147,8 +165,51 @@ test_expect_success '__gitcomp - suffix' ' EOF ' +test_expect_success '__gitcomp - doesnt fail because of invalid variable name' ' + __gitcomp "$invalid_variable_name" +' + +read -r -d "" refs <<-\EOF +maint +master +next +pu +EOF + +test_expect_success '__gitcomp_nl - trailing space' ' + test_gitcomp_nl "m" "$refs" <<-EOF + maint Z + master Z + EOF +' + +test_expect_success '__gitcomp_nl - prefix' ' + test_gitcomp_nl "--fixup=m" "$refs" "--fixup=" "m" <<-EOF + --fixup=maint Z + --fixup=master Z + EOF +' + +test_expect_success '__gitcomp_nl - suffix' ' + test_gitcomp_nl "branch.ma" "$refs" "branch." "ma" "." <<-\EOF + branch.maint.Z + branch.master.Z + EOF +' + +test_expect_success '__gitcomp_nl - no suffix' ' + test_gitcomp_nl "ma" "$refs" "" "ma" "" <<-\EOF + maintZ + masterZ + EOF +' + +test_expect_success '__gitcomp_nl - doesnt fail because of invalid variable name' ' + __gitcomp_nl "$invalid_variable_name" +' + test_expect_success 'basic' ' - run_completion "git \"\"" && + run_completion "git " && # built-in grep -q "^add \$" out && # script @@ -271,7 +332,7 @@ test_expect_success 'complete tree filename with spaces' ' EOF ' -test_expect_failure 'complete tree filename with metacharacters' ' +test_expect_success 'complete tree filename with metacharacters' ' echo content >"name with \${meta}" && git add . && git commit -m meta && diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 2101d914f2..e147a8d277 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -59,7 +59,7 @@ test_expect_success 'gitdir - .git directory in cwd' ' ' test_expect_success 'gitdir - .git directory in parent' ' - echo "$TRASH_DIRECTORY/.git" > expected && + echo "$(pwd -P)/.git" > expected && ( cd subdir/subsubdir && __gitdir > "$actual" @@ -77,7 +77,7 @@ test_expect_success 'gitdir - cwd is a .git directory' ' ' test_expect_success 'gitdir - parent is a .git directory' ' - echo "$TRASH_DIRECTORY/.git" > expected && + echo "$(pwd -P)/.git" > expected && ( cd .git/refs/heads && __gitdir > "$actual" @@ -115,7 +115,7 @@ test_expect_success 'gitdir - non-existing $GIT_DIR' ' ' test_expect_success 'gitdir - gitfile in cwd' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "$(pwd -P)/otherrepo/.git" > expected && echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && test_when_finished "rm -f subdir/.git" && ( @@ -126,7 +126,7 @@ test_expect_success 'gitdir - gitfile in cwd' ' ' test_expect_success 'gitdir - gitfile in parent' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "$(pwd -P)/otherrepo/.git" > expected && echo "gitdir: $TRASH_DIRECTORY/otherrepo/.git" > subdir/.git && test_when_finished "rm -f subdir/.git" && ( @@ -137,7 +137,7 @@ test_expect_success 'gitdir - gitfile in parent' ' ' test_expect_success SYMLINKS 'gitdir - resulting path avoids symlinks' ' - echo "$TRASH_DIRECTORY/otherrepo/.git" > expected && + echo "$(pwd -P)/otherrepo/.git" > expected && mkdir otherrepo/dir && test_when_finished "rm -rf otherrepo/dir" && ln -s otherrepo/dir link && diff --git a/t/test-lib.sh b/t/test-lib.sh index debd8b4751..ca6bdef63d 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -600,14 +600,14 @@ then fi # Test repository -test="trash directory.$(basename "$0" .sh)" -test -n "$root" && test="$root/$test" -case "$test" in -/*) TRASH_DIRECTORY="$test" ;; - *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$test" ;; +TRASH_DIRECTORY="trash directory.$(basename "$0" .sh)" +test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" +case "$TRASH_DIRECTORY" in +/*) ;; # absolute path is good + *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; esac test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY -rm -fr "$test" || { +rm -fr "$TRASH_DIRECTORY" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" exit 1 @@ -618,13 +618,13 @@ export HOME if test -z "$TEST_NO_CREATE_REPO" then - test_create_repo "$test" + test_create_repo "$TRASH_DIRECTORY" else - mkdir -p "$test" + mkdir -p "$TRASH_DIRECTORY" fi # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). -cd -P "$test" || exit 1 +cd -P "$TRASH_DIRECTORY" || exit 1 this_test=${0##*/} this_test=${this_test%%-*} |