diff options
112 files changed, 2003 insertions, 477 deletions
diff --git a/Documentation/RelNotes/2.9.0.txt b/Documentation/RelNotes/2.9.0.txt new file mode 100644 index 0000000000..7bf95f8519 --- /dev/null +++ b/Documentation/RelNotes/2.9.0.txt @@ -0,0 +1,220 @@ +Git 2.9 Release Notes +===================== + +Backward compatibility note +--------------------------- + +The end-user facing Porcelain level commands in the "git diff" and +"git log" by default enables the rename detection; you can still use +"diff.renames" configuration variable to disable this. + +Merging two branches that have no common ancestor with "git merge" is +by default forbidden now to prevent creating such an unusual merge by +mistake. + +The output formats of "git log" that indents the commit log message by +4 spaces now expands HT in the log message by default. You can use +the "--no-expand-tabs" option to disable this. + + +Updates since v2.8 +------------------ + +UI, Workflows & Features + + * The end-user facing Porcelain level commands like "diff" and "log" + now enables the rename detection by default. + + * The credential.helper configuration variable is cumulative and + there is no good way to override it from the command line. As + a special case, giving an empty string as its value now serves + as the signal to clear the values specified in various files. + + * A new "interactive.diffFilter" configuration can be used to + customize the diff shown in "git add -i" session. + + * "git p4" now allows P4 author names to be mapped to Git author + names. + + * "git rebase -x" can be used without passing "-i" option. + + * "git -c credential.<var>=<value> submodule" can now be used to + propagate configuration variables related to credential helper + down to the submodules. + + * "git tag" can create an annotated tag without explicitly given an + "-a" (or "-s") option (i.e. when a tag message is given). A new + configuration variable, tag.forceSignAnnotated, can be used to tell + the command to create signed tag in such a situation. + + * "git merge" used to allow merging two branches that have no common + base by default, which led to a brand new history of an existing + project created and then get pulled by an unsuspecting maintainer, + which allowed an unnecessary parallel history merged into the + existing project. The command has been taught not to allow this by + default, with an escape hatch "--allow-unrelated-histories" option + to be used in a rare event that merges histories of two projects + that started their lives independently. + + * "git apply -v" learned to report paths in the patch that were + skipped via --include/--exclude mechanism or being outside the + current working directory. + + * Shell completion (in contrib/) updates. + + * The commit object name reported when "rebase -i" stops has been + shortened. + + * "git worktree add" can be given "--no-checkout" option to only + create an empty worktree without checking out the files. + + * "git mergetools" learned to drive ExamDiff. + + * "git pull --rebase" learned "--[no-]autostash" option, so that + the rebase.autostash configuration variable set to true can be + overridden from the command line. + + * When "git log" shows the log message indented by 4-spaces, the + remainder of a line after a HT does not align in the way the author + originally intended. The command now expands tabs by default in + such a case, and allows the users to override it with a new option, + "--no-expand-tabs". + + +Performance, Internal Implementation, Development Support etc. + + * The embedded args argv-array in the child process is used to build + the command line to run pack-objects instead of using a separate + array of strings. + (merge 65a3629 mp/upload-pack-use-embedded-args later to maint). + + * A test for tags has been restructured so that more parts of it can + easily be run on a platform without a working GnuPG. + + * The startup_info data, which records if we are working inside a + repository (among other things), are now uniformly available to Git + subcommand implementations, and Git avoids attempting to touch + references when we are not in a repository. + (merge 11e6b3f jk/startup-info later to maint). + + * The command line argument parser for "receive-pack" has been + rewritten to use parse-options. + + * A major part of "git submodule update" has been ported to C to take + advantage of the recently added framework to run download tasks in + parallel. + + * Rename bunch of tests on "git clone" for better organization. + (merge 8fbb03a sb/clone-t57-t56 later to maint). + + * The tests that involve running httpd leaked the system-wide + configuration in /etc/gitconfig to the tested environment. + (merge 1fad503 jk/test-httpd-config-nosystem later to maint). + + * Build updates for MSVC. + (merge 0ef60af ss/msvc later to maint). + + * The repository set-up sequence has been streamlined (the biggest + change is that there is no longer git_config_early()), so that we + do not attempt to look into refs/* when we know we do not have a + Git repository. + (merge 274db84 jk/check-repository-format later to maint). + + +Also contains various documentation updates and code clean-ups. + + +Fixes since v2.8 +---------------- + +Unless otherwise noted, all the fixes since v2.8 in the maintenance +track are contained in this release (see the maintenance releases' +notes for details). + + * "git config --get-urlmatch", unlike other variants of the "git + config --get" family, did not signal error with its exit status + when there was no matching configuration. + (merge 24990b2 jk/config-get-urlmatch later to maint). + + * The "--local-env-vars" and "--resolve-git-dir" options of "git + rev-parse" failed to work outside a repository when the command's + option parsing was rewritten in 1.8.5 era. + (merge fc7d47f jk/rev-parse-local-env-vars later to maint). + + * "git index-pack --keep[=<msg>] pack-$name.pack" simply did not work. + (merge 0e94242 jc/maint-index-pack-keep later to maint). + + * Fetching of history by naming a commit object name directly didn't + work across remote-curl transport. + (merge 754ecb1 gf/fetch-pack-direct-object-fetch later to maint). + + * A small memory leak in an error codepath has been plugged in xdiff + code. + (merge 87f1625 rj/xdiff-prepare-plug-leak-on-error-codepath later to maint). + + * strbuf_getwholeline() did not NUL-terminate the buffer on certain + corner cases in its error codepath. + (merge b709043 jk/getwholeline-getdelim-empty later to maint). + + * "git mergetool" did not work well with conflicts that both sides + deleted. + (merge a298604 da/mergetool-delete-delete-conflict later to maint). + + * "git send-email" had trouble parsing alias file in mailrc format + when lines in it had trailing whitespaces on them. + (merge a277d1e jk/send-email-rtrim-mailrc-alias later to maint). + + * When "git merge --squash" stopped due to conflict, the concluding + "git commit" failed to read in the SQUASH_MSG that shows the log + messages from all the squashed commits. + (merge b64c1e0 ss/commit-squash-msg later to maint). + + * "git merge FETCH_HEAD" dereferenced NULL pointer when merging + nothing into an unborn history (which is arguably unusual usage, + which perhaps was the reason why nobody noticed it). + (merge b84e65d jv/merge-nothing-into-void later to maint). + + * When "git worktree" feature is in use, "git branch -d" allowed + deletion of a branch that is checked out in another worktree, + which was wrong. + (merge f292244 ky/branch-d-worktree later to maint). + + * "git diff -M" used to work better when two originally identical + files A and B got renamed to X/A and X/B by pairing A to X/A and B + to X/B, but this was broken in the 2.0 timeframe. + (merge ca4e3ca sg/diff-multiple-identical-renames later to maint). + + * "git send-pack --all <there>" was broken when its command line + option parsing was written in the 2.6 timeframe. + (merge c677756 sk/send-pack-all-fix later to maint). + + * "git format-patch --help" showed `-s` and `--no-patch` as if these + are valid options to the command. We already hide `--patch` option + from the documentation, because format-patch is about showing the + diff, and the documentation now hides these options as well. + (merge b73a1bc es/format-patch-doc-hide-no-patch later to maint). + + * When running "git blame $path" with unnormalized data in the index + for the path, the data in the working tree was blamed, even though + "git add" would not have changed what is already in the index, due + to "safe crlf" that disables the line-end conversion. It has been + corrected. + (merge a08feb8 tb/blame-force-read-cache-to-workaround-safe-crlf later to maint). + + * Other minor clean-ups and documentation updates + (merge aed7480 mm/lockfile-error-message later to maint). + (merge bfee614 jc/index-pack later to maint). + (merge f870899 ss/exc-flag-is-a-collection-of-bits later to maint). + (merge dde7891 pb/t7502-drop-dup later to maint). + (merge 3bd1b51 cc/doc-recommend-performance-trace-to-file later to maint). + (merge 7d5e9c9 jk/credential-cache-comment-exit later to maint). + (merge 16a86d4 nd/apply-doc later to maint). + (merge c3f6b85 pb/opt-cmdmode-doc later to maint). + (merge 30211fb oa/doc-diff-check later to maint). + (merge 01d98e8 ak/use-hashmap-iter-first-in-submodule-config later to maint). + (merge 8b5a3e9 kn/for-each-tag-branch later to maint). + (merge 9c60d9f sb/misc-cleanups later to maint). + (merge 7a6a44c cc/apply later to maint). + (merge 8e9b208 js/mingw-tests-2.8 later to maint). + (merge d55de70 jc/makefile-redirection-stderr later to maint). + (merge 4232b21 ep/trace-doc-sample-fix later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 2cd6bdd7d2..42d2b50477 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1113,8 +1113,9 @@ commit.template:: credential.helper:: Specify an external helper to be called when a username or password credential is needed; the helper may consult external - storage to avoid prompting the user for the credentials. See - linkgit:gitcredentials[7] for details. + storage to avoid prompting the user for the credentials. Note + that multiple helpers may be defined. See linkgit:gitcredentials[7] + for details. credential.useHttpPath:: When acquiring credentials, consider the "path" component of an http @@ -1886,6 +1887,14 @@ interactive.singleKey:: setting is silently ignored if portable keystroke input is not available; requires the Perl module Term::ReadKey. +interactive.diffFilter:: + When an interactive command (such as `git add --patch`) shows + a colorized diff, git will pipe the diff through the shell + command defined by this configuration variable. The command may + mark up the diff further for human consumption, provided that it + retains a one-to-one correspondence with the lines in the + original diff. Defaults to disabled (no filtering). + log.abbrevCommit:: If true, makes linkgit:git-log[1], linkgit:git-show[1], and linkgit:git-whatchanged[1] assume `--abbrev-commit`. You may @@ -2729,6 +2738,17 @@ submodule.<name>.ignore:: "--ignore-submodules" option. The 'git submodule' commands are not affected by this setting. +submodule.fetchJobs:: + Specifies how many submodules are fetched/cloned at the same time. + A positive integer allows up to that number of submodules fetched + in parallel. A value of 0 will give some reasonable default. + If unset, it defaults to 1. + +tag.forceSignAnnotated:: + A boolean to specify whether annotated tags created should be GPG signed. + If `--annotate` is specified on the command line, it takes + precedence over this option. + tag.sort:: This variable controls the sort ordering of tags when displayed by linkgit:git-tag[1]. Without the "--sort=<value>" option provided, the diff --git a/Documentation/diff-config.txt b/Documentation/diff-config.txt index 6eaa45271c..edba56522b 100644 --- a/Documentation/diff-config.txt +++ b/Documentation/diff-config.txt @@ -108,9 +108,13 @@ diff.renameLimit:: detection; equivalent to the 'git diff' option '-l'. diff.renames:: - Tells Git to detect renames. If set to any boolean value, it - will enable basic rename detection. If set to "copies" or - "copy", it will detect copies, as well. + Whether and how Git detects renames. If set to "false", + rename detection is disabled. If set to "true", basic rename + detection is enabled. If set to "copies" or "copy", Git will + detect copies, as well. Defaults to true. Note that this + affects only 'git diff' Porcelain like linkgit:git-diff[1] and + linkgit:git-log[1], and not lower level commands such as + linkgit:git-diff-files[1]. diff.suppressBlankEmpty:: A boolean to inhibit the standard behavior of printing a space diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 32f48ed647..4b0318e2ac 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -26,12 +26,12 @@ ifndef::git-format-patch[] ifdef::git-diff[] This is the default. endif::git-diff[] -endif::git-format-patch[] -s:: --no-patch:: Suppress diff output. Useful for commands like `git show` that show the patch by default, or to cancel the effect of `--patch`. +endif::git-format-patch[] -U<n>:: --unified=<n>:: diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index b7c467a001..45d74be297 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -14,7 +14,7 @@ SYNOPSIS [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>] [--dissociate] [--separate-git-dir <git dir>] [--depth <depth>] [--[no-]single-branch] - [--recursive | --recurse-submodules] [--] <repository> + [--recursive | --recurse-submodules] [--jobs <n>] [--] <repository> [<directory>] DESCRIPTION @@ -219,6 +219,10 @@ objects from the source repository into a pack in the cloned repository. The result is Git repository can be separated from working tree. +-j <n>:: +--jobs <n>:: + The number of submodules fetched at the same time. + Defaults to the `submodule.fetchJobs` option. <repository>:: The (possibly remote) repository to clone from. See the diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 012e8f9a08..c52578bb87 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -76,7 +76,7 @@ OPTIONS specified commit (HEAD if not specified). --contains [<object>]:: - Only list tags which contain the specified commit (HEAD if not + Only list refs which contain the specified commit (HEAD if not specified). FIELD NAMES diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 07f7295ec8..689aa4c57c 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -98,6 +98,19 @@ commit or stash your changes before running 'git merge'. 'git merge --abort' is equivalent to 'git reset --merge' when `MERGE_HEAD` is present. +--allow-unrelated-histories:: + By default, `git merge` command refuses to merge histories + that do not share a common ancestor. This option can be + used to override this safety when merging histories of two + projects that started their lives independently. As that is + a very rare occasion, no configuration variable to enable + this by default exists and will not be added, and the list + of options at the top of this documentation does not mention + this option. Also `git pull` does not pass this option down + to `git merge` (instead, you `git fetch` first, examine what + you will be merging and then `git merge` locally with this + option). + <commit>...:: Commits, usually other branch heads, to merge into our branch. Specifying more than one commit will create a merge with diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt index 35e3170918..88ba42b455 100644 --- a/Documentation/git-p4.txt +++ b/Documentation/git-p4.txt @@ -551,6 +551,17 @@ git-p4.keepEmptyCommits:: A changelist that contains only excluded files will be imported as an empty commit if this boolean option is set to true. +git-p4.mapUser:: + Map a P4 user to a name and email address in Git. Use a string + with the following format to create a mapping: ++ +------------- +git config --add git-p4.mapUser "p4user = First Last <mail@address.com>" +------------- ++ +A mapping will override any user information from P4. Mappings for +multiple P4 user can be defined. + Submit variables ~~~~~~~~~~~~~~~~ git-p4.detectRenames:: diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index a62a2a615d..d033b258e5 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -128,6 +128,15 @@ unless you have read linkgit:git-rebase[1] carefully. --no-rebase:: Override earlier --rebase. +--autostash:: +--no-autostash:: + Before starting rebase, stash local modifications away (see + linkgit:git-stash[1]) if needed, and apply the stash when + done. `--no-autostash` is useful to override the `rebase.autoStash` + configuration variable (see linkgit:git-config[1]). ++ +This option is only valid when "--rebase" is used. + Options related to fetching ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6ed610a031..0387b40e0a 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -391,9 +391,6 @@ idea unless you know what you are doing (see BUGS below). final history. <cmd> will be interpreted as one or more shell commands. + -This option can only be used with the `--interactive` option -(see INTERACTIVE MODE below). -+ You may execute several commands by either using one instance of `--exec` with several commands: + @@ -406,6 +403,9 @@ or by giving more than one `--exec`: If `--autosquash` is used, "exec" lines will not be appended for the intermediate commits, and will only appear at the end of each squash/fixup series. ++ +This uses the `--interactive` machinery internally, but it can be run +without an explicit `--interactive`. --root:: Rebase all commits reachable from <branch>, instead of diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 1572f058f5..13adebf7b7 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -16,7 +16,7 @@ SYNOPSIS 'git submodule' [--quiet] deinit [-f|--force] [--] <path>... 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase|--merge] [--reference <repository>] - [--depth <depth>] [--recursive] [--] [<path>...] + [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...] 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...] 'git submodule' [--quiet] foreach [--recursive] <command> @@ -377,6 +377,11 @@ for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully. clone with a history truncated to the specified number of revisions. See linkgit:git-clone[1] +-j <n>:: +--jobs <n>:: + This option is only valid for the update command. + Clone new submodules in parallel with as many jobs. + Defaults to the `submodule.fetchJobs` option. <path>...:: Paths to submodule(s). When specified this will restrict the command diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 62c76c1c89..c62234538b 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees SYNOPSIS -------- [verse] -'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>] +'git worktree add' [-f] [--detach] [--checkout] [-b <new-branch>] <path> [<branch>] 'git worktree prune' [-n] [-v] [--expire <expire>] 'git worktree list' [--porcelain] @@ -87,6 +87,12 @@ OPTIONS With `add`, detach HEAD in the new working tree. See "DETACHED HEAD" in linkgit:git-checkout[1]. +--[no-]checkout:: + By default, `add` checks out `<branch>`, however, `--no-checkout` can + be used to suppress checkout in order to make customizations, + such as configuring sparse-checkout. See "Sparse checkout" + in linkgit:git-read-tree[1]. + -n:: --dry-run:: With `prune`, do not remove anything; just report what it would diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt index 1c75be0803..f3a75d1ce1 100644 --- a/Documentation/gitcredentials.txt +++ b/Documentation/gitcredentials.txt @@ -106,6 +106,11 @@ variable, each helper will be tried in turn, and may provide a username, password, or nothing. Once Git has acquired both a username and a password, no more helpers will be tried. +If `credential.helper` is configured to the empty string, this resets +the helper list to empty (so you may override a helper set by a +lower-priority config file by configuring the empty-string helper, +followed by whatever set of helpers you would like). + CREDENTIAL CONTEXTS ------------------- diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 54b88b6dca..6c67182728 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -42,6 +42,20 @@ people using 80-column terminals. verbatim; this means that invalid sequences in the original commit may be copied to the output. +--expand-tabs=<n>:: +--expand-tabs:: +--no-expand-tabs:: + Perform a tab expansion (replace each tab with enough spaces + to fill to the next display column that is multiple of '<n>') + in the log message before showing it in the output. + `--expand-tabs` is a short-hand for `--expand-tabs=8`, and + `--no-expand-tabs` is a short-hand for `--expand-tabs=0`, + which disables tab expansion. ++ +By default, tabs are expanded in pretty formats that indent the log +message by 4 spaces (i.e. 'medium', which is the default, 'full', +and 'fuller'). + ifndef::git-rev-list[] --notes[=<treeish>]:: Show the notes (see linkgit:git-notes[1]) that annotate the diff --git a/Documentation/technical/api-config.txt b/Documentation/technical/api-config.txt index 0d8b99b368..20741f345e 100644 --- a/Documentation/technical/api-config.txt +++ b/Documentation/technical/api-config.txt @@ -63,13 +63,6 @@ parse for configuration, rather than looking in the usual files. Regular Specify whether include directives should be followed in parsed files. Regular `git_config` defaults to `1`. -There is a special version of `git_config` called `git_config_early`. -This version takes an additional parameter to specify the repository -config, instead of having it looked up via `git_path`. This is useful -early in a Git program before the repository has been found. Unless -you're working with early setup code, you probably don't want to use -this. - Reading Specific Files ---------------------- diff --git a/Documentation/technical/api-trace.txt b/Documentation/technical/api-trace.txt index 389ae16d15..fadb5979c4 100644 --- a/Documentation/technical/api-trace.txt +++ b/Documentation/technical/api-trace.txt @@ -28,7 +28,7 @@ static struct trace_key trace_foo = TRACE_KEY_INIT(FOO); static void trace_print_foo(const char *message) { - trace_print_key(&trace_foo, message); + trace_printf_key(&trace_foo, "%s", message); } ------------ + diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 46595dad22..655b49011f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.8.1 +DEF_VER=v2.8.0.GIT LF=' ' @@ -2263,10 +2263,10 @@ sparse: $(SP_OBJ) check: common-cmds.h @if sparse; \ then \ - echo 2>&1 "Use 'make sparse' instead"; \ + echo >&2 "Use 'make sparse' instead"; \ $(MAKE) --no-print-directory sparse; \ else \ - echo 2>&1 "Did you mean 'make test'?"; \ + echo >&2 "Did you mean 'make test'?"; \ exit 1; \ fi @@ -17,11 +17,11 @@ including full documentation and Git related tools. See [Documentation/gittutorial.txt][] to get started, then see [Documentation/giteveryday.txt][] for a useful minimum set of commands, and -[Documentation/git-commandname.txt][] for documentation of each command. +Documentation/git-*commandname*.txt for documentation of each command. If git has been correctly installed, then the tutorial can also be read with "man gittutorial" or "git help tutorial", and the -documentation of each command with "man git-commandname" or "git help -commandname". +documentation of each command with "man git-*commandname*" or "git help +*commandname*". CVS users may also want to read [Documentation/gitcvs-migration.txt][] ("man gitcvs-migration" or "git help cvs-migration" if git is @@ -57,6 +57,5 @@ and the name as (depending on your mood): [INSTALL]: INSTALL [Documentation/gittutorial.txt]: Documentation/gittutorial.txt [Documentation/giteveryday.txt]: Documentation/giteveryday.txt -[Documentation/git-commandname.txt]: Documentation/git-commandname.txt [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt [Documentation/SubmittingPatches]: Documentation/SubmittingPatches @@ -1 +1 @@ -Documentation/RelNotes/2.8.1.txt
\ No newline at end of file +Documentation/RelNotes/2.9.0.txt
\ No newline at end of file @@ -167,7 +167,6 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) strbuf_add(&path, pfx, pfx_len); strbuf_addstr(&path, arg); #else - char *p; /* don't add prefix to absolute paths, but still replace '\' by '/' */ strbuf_reset(&path); if (is_absolute_path(arg)) @@ -175,9 +174,7 @@ const char *prefix_filename(const char *pfx, int pfx_len, const char *arg) else if (pfx_len) strbuf_add(&path, pfx, pfx_len); strbuf_addstr(&path, arg); - for (p = path.buf + pfx_len; *p; p++) - if (*p == '\\') - *p = '/'; + convert_slashes(path.buf + pfx_len); #endif return path.buf; } diff --git a/builtin/apply.c b/builtin/apply.c index 42c610e2ec..8e4da2e1bd 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -931,22 +931,19 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, return find_name(line, NULL, p_value, TERM_TAB); if (orig_name) { - int len; - const char *name; + int len = strlen(orig_name); char *another; - name = orig_name; - len = strlen(name); if (isnull) - die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr); + die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), + orig_name, linenr); another = find_name(line, NULL, p_value, TERM_TAB); - if (!another || memcmp(another, name, len + 1)) + if (!another || memcmp(another, orig_name, len + 1)) die((side == DIFF_NEW_NAME) ? _("git apply: bad git-diff - inconsistent new filename on line %d") : _("git apply: bad git-diff - inconsistent old filename on line %d"), linenr); free(another); return orig_name; - } - else { + } else { /* expect "/dev/null" */ if (memcmp("/dev/null", line, 9) || line[9] != '\n') die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr); @@ -956,21 +953,15 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, static int gitdiff_oldname(const char *line, struct patch *patch) { - char *orig = patch->old_name; patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name, DIFF_OLD_NAME); - if (orig != patch->old_name) - free(orig); return 0; } static int gitdiff_newname(const char *line, struct patch *patch) { - char *orig = patch->new_name; patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name, DIFF_NEW_NAME); - if (orig != patch->new_name) - free(orig); return 0; } @@ -1872,6 +1863,11 @@ static struct fragment *parse_binary_hunk(char **buf_p, return NULL; } +/* + * Returns: + * -1 in case of error, + * the length of the parsed binary patch otherwise + */ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) { /* @@ -2017,6 +2013,8 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) linenr++; used = parse_binary(buffer + hd + llen, size - hd - llen, patch); + if (used < 0) + return -1; if (used) patchsize = used + llen; else @@ -4373,8 +4371,10 @@ static int apply_patch(int fd, const char *filename, int options) patch->inaccurate_eof = !!(options & INACCURATE_EOF); patch->recount = !!(options & RECOUNT); nr = parse_chunk(buf.buf + offset, buf.len - offset, patch); - if (nr < 0) + if (nr < 0) { + free_patch(patch); break; + } if (apply_in_reverse) reverse_patches(patch); if (use_patch(patch)) { @@ -4383,6 +4383,8 @@ static int apply_patch(int fd, const char *filename, int options) listp = &patch->next; } else { + if (apply_verbosely) + say_patch_name(stderr, _("Skipped patch '%s'."), patch); free_patch(patch); skipped_patch++; } diff --git a/builtin/blame.c b/builtin/blame.c index e982fb8137..21f42b0b62 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -2307,6 +2307,7 @@ static struct commit *fake_working_tree_commit(struct diff_options *opt, unsigned mode; struct strbuf msg = STRBUF_INIT; + read_cache(); time(&now); commit = alloc_commit_node(); commit->object.parsed = 1; diff --git a/builtin/branch.c b/builtin/branch.c index 7b45b6bd6b..8885d9f8e2 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -20,6 +20,7 @@ #include "utf8.h" #include "wt-status.h" #include "ref-filter.h" +#include "worktree.h" static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), @@ -215,16 +216,21 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int flags = 0; strbuf_branchname(&bname, argv[i]); - if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) { - error(_("Cannot delete the branch '%s' " - "which you are currently on."), bname.buf); - ret = 1; - continue; - } - free(name); - name = mkpathdup(fmt, bname.buf); + + if (kinds == FILTER_REFS_BRANCHES) { + char *worktree = find_shared_symref("HEAD", name); + if (worktree) { + error(_("Cannot delete branch '%s' " + "checked out at '%s'"), + bname.buf, worktree); + free(worktree); + ret = 1; + continue; + } + } + target = resolve_ref_unsafe(name, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE diff --git a/builtin/clone.c b/builtin/clone.c index 661639255c..6576ecf343 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -51,6 +51,7 @@ static enum transport_family family; static struct string_list option_config; static struct string_list option_reference; static int option_dissociate; +static int max_jobs = -1; static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), @@ -73,6 +74,8 @@ static struct option builtin_clone_options[] = { N_("initialize submodules in the clone")), OPT_BOOL(0, "recurse-submodules", &option_recursive, N_("initialize submodules in the clone")), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), N_("directory from which templates will be used")), OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"), @@ -100,10 +103,6 @@ static struct option builtin_clone_options[] = { OPT_END() }; -static const char *argv_submodule[] = { - "submodule", "update", "--init", "--recursive", NULL -}; - static const char *get_repo_path_1(struct strbuf *path, int *is_bundle) { static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; @@ -732,8 +731,16 @@ static int checkout(void) err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1), sha1_to_hex(sha1), "1", NULL); - if (!err && option_recursive) - err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); + if (!err && option_recursive) { + struct argv_array args = ARGV_ARRAY_INIT; + argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL); + + if (max_jobs != -1) + argv_array_pushf(&args, "--jobs=%d", max_jobs); + + err = run_command_v_opt(args.argv, RUN_GIT_CMD); + argv_array_clear(&args); + } return err; } diff --git a/builtin/commit.c b/builtin/commit.c index c733ec98b7..98e15276df 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -186,6 +186,7 @@ static void status_init_config(struct wt_status *s, config_fn_t fn) gitmodules_config(); git_config(fn, s); determine_whence(s); + init_diff_ui_defaults(); s->hints = advice_status_hints; /* must come after git_config() */ } diff --git a/builtin/diff.c b/builtin/diff.c index 52c98a9217..343c6b8f25 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -318,6 +318,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) if (!no_index) gitmodules_config(); + init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); init_revisions(&rev, prefix); diff --git a/builtin/fetch.c b/builtin/fetch.c index e4639d8eb1..f8455bde7a 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -37,7 +37,7 @@ static int prune = -1; /* unspecified */ static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity; static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT, unshallow, update_shallow; -static int max_children = 1; +static int max_children = -1; static enum transport_family family; static const char *depth; static const char *upload_pack; diff --git a/builtin/init-db.c b/builtin/init-db.c index da531f6b76..b2d8d40a67 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -95,6 +95,8 @@ static void copy_templates(const char *template_dir) struct strbuf path = STRBUF_INIT; struct strbuf template_path = STRBUF_INIT; size_t template_len; + struct repository_format template_format; + struct strbuf err = STRBUF_INIT; DIR *dir; char *to_free = NULL; @@ -121,17 +123,18 @@ static void copy_templates(const char *template_dir) /* Make sure that template is from the correct vintage */ strbuf_addstr(&template_path, "config"); - repository_format_version = 0; - git_config_from_file(check_repository_format_version, - template_path.buf, NULL); + read_repository_format(&template_format, template_path.buf); strbuf_setlen(&template_path, template_len); - if (repository_format_version && - repository_format_version != GIT_REPO_VERSION) { - warning(_("not copying templates of " - "a wrong format version %d from '%s'"), - repository_format_version, - template_dir); + /* + * No mention of version at all is OK, but anything else should be + * verified. + */ + if (template_format.version >= 0 && + verify_repository_format(&template_format, &err) < 0) { + warning(_("not copying templates from '%s': %s"), + template_dir, err.buf); + strbuf_release(&err); goto close_free_return; } @@ -199,13 +202,13 @@ static int create_default_files(const char *template_path) /* reading existing config may have overwrote it */ if (init_shared_repository != -1) - shared_repository = init_shared_repository; + set_shared_repository(init_shared_repository); /* * We would have created the above under user's umask -- under * shared-repository settings, we would need to fix them up. */ - if (shared_repository) { + if (get_shared_repository()) { adjust_shared_perm(get_git_dir()); adjust_shared_perm(git_path_buf(&buf, "refs")); adjust_shared_perm(git_path_buf(&buf, "refs/heads")); @@ -370,7 +373,7 @@ int init_db(const char *template_dir, unsigned int flags) create_object_directory(); - if (shared_repository) { + if (get_shared_repository()) { char buf[10]; /* We do not spell "group" and such, so that * the configuration can be read by older version @@ -378,12 +381,12 @@ int init_db(const char *template_dir, unsigned int flags) * and compatibility values for PERM_GROUP and * PERM_EVERYBODY. */ - if (shared_repository < 0) + if (get_shared_repository() < 0) /* force to the mode value */ - xsnprintf(buf, sizeof(buf), "0%o", -shared_repository); - else if (shared_repository == PERM_GROUP) + xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository()); + else if (get_shared_repository() == PERM_GROUP) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); - else if (shared_repository == PERM_EVERYBODY) + else if (get_shared_repository() == PERM_EVERYBODY) xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); else die("BUG: invalid value for shared_repository"); @@ -399,7 +402,7 @@ int init_db(const char *template_dir, unsigned int flags) "", and the last '%s%s' is the verbatim directory name. */ printf(_("%s%s Git repository in %s%s\n"), reinit ? _("Reinitialized existing") : _("Initialized empty"), - shared_repository ? _(" shared") : "", + get_shared_repository() ? _(" shared") : "", git_dir, len && git_dir[len-1] != '/' ? "/" : ""); } @@ -494,8 +497,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * and we know shared_repository should always be 0; * but just in case we play safe. */ - saved = shared_repository; - shared_repository = 0; + saved = get_shared_repository(); + set_shared_repository(0); switch (safe_create_leading_directories_const(argv[0])) { case SCLD_OK: case SCLD_PERMS: @@ -507,7 +510,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) die_errno(_("cannot mkdir %s"), argv[0]); break; } - shared_repository = saved; + set_shared_repository(saved); if (mkdir(argv[0], 0777) < 0) die_errno(_("cannot mkdir %s"), argv[0]); mkdir_tried = 1; @@ -525,7 +528,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } if (init_shared_repository != -1) - shared_repository = init_shared_repository; + set_shared_repository(init_shared_repository); /* * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR diff --git a/builtin/log.c b/builtin/log.c index 0d738d6ddc..dff3fbbb43 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -100,6 +100,12 @@ static int log_line_range_callback(const struct option *option, const char *arg, return 0; } +static void init_log_defaults(void) +{ + init_grep_defaults(); + init_diff_ui_defaults(); +} + static void cmd_log_init_defaults(struct rev_info *rev) { if (fmt_pretty) @@ -416,7 +422,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -527,7 +533,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) struct pathspec match_all; int i, count, ret = 0; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); memset(&match_all, 0, sizeof(match_all)); @@ -608,7 +614,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -647,7 +653,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) struct rev_info rev; struct setup_revision_opt opt; - init_grep_defaults(); + init_log_defaults(); git_config(git_log_config, NULL); init_revisions(&rev, prefix); @@ -1280,10 +1286,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_hdr.strdup_strings = 1; extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; - init_grep_defaults(); + init_log_defaults(); git_config(git_format_config, NULL); init_revisions(&rev, prefix); rev.commit_format = CMIT_FMT_EMAIL; + rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; rev.max_parents = 1; diff --git a/builtin/merge.c b/builtin/merge.c index bf2f2614fb..41467e4277 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -64,6 +64,7 @@ static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; +static int allow_unrelated_histories; static int show_progress = -1; static int default_to_upstream = 1; static const char *sign_commit; @@ -221,6 +222,8 @@ static struct option builtin_merge_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), + OPT_BOOL(0, "allow-unrelated-histories", &allow_unrelated_histories, + N_("allow merging unrelated histories")), OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, @@ -1187,6 +1190,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else head_commit = lookup_commit_or_die(head_sha1, "HEAD"); + init_diff_ui_defaults(); git_config(git_merge_config, NULL); if (branch_mergeoptions) @@ -1397,9 +1401,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.oid.hash, NULL, 0, UPDATE_REFS_DIE_ON_ERR); - if (remoteheads && !common) - ; /* No common ancestors found. We need a real merge. */ - else if (!remoteheads || + if (remoteheads && !common) { + /* No common ancestors found. */ + if (!allow_unrelated_histories) + die(_("refusing to merge unrelated histories")); + /* otherwise, we need a real merge. */ + } else if (!remoteheads || (!remoteheads->next && !common->next && common->item == remoteheads->item)) { /* diff --git a/builtin/notes.c b/builtin/notes.c index ed6f2222f4..6fd058de92 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -744,13 +744,14 @@ static int merge_commit(struct notes_merge_options *o) static int git_config_get_notes_strategy(const char *key, enum notes_merge_strategy *strategy) { - const char *value; + char *value; - if (git_config_get_string_const(key, &value)) + if (git_config_get_string(key, &value)) return 1; if (parse_notes_merge_strategy(value, strategy)) git_die_config(key, "unknown notes merge strategy %s", value); + free(value); return 0; } diff --git a/builtin/pull.c b/builtin/pull.c index 10eff03967..d98f481d31 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -86,6 +86,8 @@ static char *opt_commit; static char *opt_edit; static char *opt_ff; static char *opt_verify_signatures; +static int opt_autostash = -1; +static int config_autostash; static struct argv_array opt_strategies = ARGV_ARRAY_INIT; static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; static char *opt_gpg_sign; @@ -149,6 +151,8 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, N_("verify that the named commit has a valid GPG signature"), PARSE_OPT_NOARG), + OPT_BOOL(0, "autostash", &opt_autostash, + N_("automatically stash/stash pop before and after rebase")), OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), N_("merge strategy to use"), 0), @@ -306,6 +310,18 @@ static enum rebase_type config_get_rebase(void) } /** + * Read config variables. + */ +static int git_pull_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, "rebase.autostash")) { + config_autostash = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); +} + +/** * Returns 1 if there are unstaged changes, 0 otherwise. */ static int has_unstaged_changes(const char *prefix) @@ -789,6 +805,10 @@ static int run_rebase(const unsigned char *curr_head, argv_array_pushv(&args, opt_strategy_opts.argv); if (opt_gpg_sign) argv_array_push(&args, opt_gpg_sign); + if (opt_autostash == 0) + argv_array_push(&args, "--no-autostash"); + else if (opt_autostash == 1) + argv_array_push(&args, "--autostash"); argv_array_push(&args, "--onto"); argv_array_push(&args, sha1_to_hex(merge_head)); @@ -823,7 +843,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase < 0) opt_rebase = config_get_rebase(); - git_config(git_default_config, NULL); + git_config(git_pull_config, NULL); if (read_cache_unmerged()) die_resolve_conflict("Pull"); @@ -834,13 +854,17 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (get_sha1("HEAD", orig_head)) hashclr(orig_head); + if (!opt_rebase && opt_autostash != -1) + die(_("--[no-]autostash option is only valid with --rebase.")); + if (opt_rebase) { - int autostash = 0; + int autostash = config_autostash; + if (opt_autostash != -1) + autostash = opt_autostash; if (is_null_sha1(orig_head) && !is_cache_unborn()) die(_("Updating an unborn branch with changes added to the index.")); - git_config_get_bool("rebase.autostash", &autostash); if (!autostash) die_on_unclean_work_tree(prefix); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c8e32b297c..220a899b96 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -21,7 +21,10 @@ #include "sigchain.h" #include "fsck.h" -static const char receive_pack_usage[] = "git receive-pack <git-dir>"; +static const char * const receive_pack_usage[] = { + N_("git receive-pack <git-dir>"), + NULL +}; enum deny_action { DENY_UNCONFIGURED, @@ -49,7 +52,7 @@ static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; -static int fix_thin = 1; +static int reject_thin; static int stateless_rpc; static const char *service_dir; static const char *head_name; @@ -1548,7 +1551,7 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (fsck_objects) argv_array_pushf(&child.args, "--strict%s", fsck_msg_types.buf); - if (fix_thin) + if (!reject_thin) argv_array_push(&child.args, "--fix-thin"); child.out = -1; child.err = err_fd; @@ -1707,45 +1710,29 @@ static int delete_only(struct command *commands) int cmd_receive_pack(int argc, const char **argv, const char *prefix) { int advertise_refs = 0; - int i; struct command *commands; struct sha1_array shallow = SHA1_ARRAY_INIT; struct sha1_array ref = SHA1_ARRAY_INIT; struct shallow_info si; - packet_trace_identity("receive-pack"); + struct option options[] = { + OPT__QUIET(&quiet, N_("quiet")), + OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL), + OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL), + OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL), + OPT_END() + }; - argv++; - for (i = 1; i < argc; i++) { - const char *arg = *argv++; + packet_trace_identity("receive-pack"); - if (*arg == '-') { - if (!strcmp(arg, "--quiet")) { - quiet = 1; - continue; - } + argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0); - if (!strcmp(arg, "--advertise-refs")) { - advertise_refs = 1; - continue; - } - if (!strcmp(arg, "--stateless-rpc")) { - stateless_rpc = 1; - continue; - } - if (!strcmp(arg, "--reject-thin-pack-for-testing")) { - fix_thin = 0; - continue; - } + if (argc > 1) + usage_msg_opt(_("Too many arguments."), receive_pack_usage, options); + if (argc == 0) + usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options); - usage(receive_pack_usage); - } - if (service_dir) - usage(receive_pack_usage); - service_dir = arg; - } - if (!service_dir) - usage(receive_pack_usage); + service_dir = argv[0]; setup_path(); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 5b9dd6a9d8..1ff5a67538 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -225,7 +225,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) * --all and --mirror are incompatible; neither makes sense * with any refspecs. */ - if ((refspecs && (send_all || args.send_mirror)) || + if ((nr_refspecs > 0 && (send_all || args.send_mirror)) || (send_all && args.send_mirror)) usage_with_options(send_pack_usage, options); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 5295b727d4..d36e8a0ec4 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -118,6 +118,55 @@ static int module_name(int argc, const char **argv, const char *prefix) return 0; } + +/* + * Rules to sanitize configuration variables that are Ok to be passed into + * submodule operations from the parent project using "-c". Should only + * include keys which are both (a) safe and (b) necessary for proper + * operation. + */ +static int submodule_config_ok(const char *var) +{ + if (starts_with(var, "credential.")) + return 1; + return 0; +} + +static int sanitize_submodule_config(const char *var, const char *value, void *data) +{ + struct strbuf *out = data; + + if (submodule_config_ok(var)) { + if (out->len) + strbuf_addch(out, ' '); + + if (value) + sq_quotef(out, "%s=%s", var, value); + else + sq_quote_buf(out, var); + } + + return 0; +} + +static void prepare_submodule_repo_env(struct argv_array *out) +{ + const char * const *var; + + for (var = local_repo_env; *var; var++) { + if (!strcmp(*var, CONFIG_DATA_ENVIRONMENT)) { + struct strbuf sanitized_config = STRBUF_INIT; + git_config_from_parameters(sanitize_submodule_config, + &sanitized_config); + argv_array_pushf(out, "%s=%s", *var, sanitized_config.buf); + strbuf_release(&sanitized_config); + } else { + argv_array_push(out, *var); + } + } + +} + static int clone_submodule(const char *path, const char *gitdir, const char *url, const char *depth, const char *reference, int quiet) { @@ -139,7 +188,7 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url argv_array_push(&cp.args, path); cp.git_cmd = 1; - cp.env = local_repo_env; + prepare_submodule_repo_env(&cp.env_array); cp.no_stdin = 1; return run_command(&cp); @@ -180,14 +229,18 @@ static int module_clone(int argc, const char **argv, const char *prefix) const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=<path>] [--quiet] " - "[--reference <repository>] [--name <name>] [--url <url>]" - "[--depth <depth>] [--] [<path>...]"), + "[--reference <repository>] [--name <name>] [--depth <depth>] " + "--url <url> --path <path>"), NULL }; argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); + if (argc || !url || !path) + usage_with_options(git_submodule_helper_usage, + module_clone_options); + strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name); sm_gitdir = strbuf_detach(&sb, NULL); @@ -249,6 +302,273 @@ static int module_clone(int argc, const char **argv, const char *prefix) return 0; } +static int module_sanitize_config(int argc, const char **argv, const char *prefix) +{ + struct strbuf sanitized_config = STRBUF_INIT; + + if (argc > 1) + usage(_("git submodule--helper sanitize-config")); + + git_config_from_parameters(sanitize_submodule_config, &sanitized_config); + if (sanitized_config.len) + printf("%s\n", sanitized_config.buf); + + strbuf_release(&sanitized_config); + + return 0; +} + +struct submodule_update_clone { + /* index into 'list', the list of submodules to look into for cloning */ + int current; + struct module_list list; + unsigned warn_if_uninitialized : 1; + + /* update parameter passed via commandline */ + struct submodule_update_strategy update; + + /* configuration parameters which are passed on to the children */ + int quiet; + const char *reference; + const char *depth; + const char *recursive_prefix; + const char *prefix; + + /* Machine-readable status lines to be consumed by git-submodule.sh */ + struct string_list projectlines; + + /* If we want to stop as fast as possible and return an error */ + unsigned quickstop : 1; +}; +#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ + SUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \ + STRING_LIST_INIT_DUP, 0} + +/** + * Determine whether 'ce' needs to be cloned. If so, prepare the 'child' to + * run the clone. Returns 1 if 'ce' needs to be cloned, 0 otherwise. + */ +static int prepare_to_clone_next_submodule(const struct cache_entry *ce, + struct child_process *child, + struct submodule_update_clone *suc, + struct strbuf *out) +{ + const struct submodule *sub = NULL; + struct strbuf displaypath_sb = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT; + const char *displaypath = NULL; + char *url = NULL; + int needs_cloning = 0; + + if (ce_stage(ce)) { + if (suc->recursive_prefix) + strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name); + else + strbuf_addf(&sb, "%s", ce->name); + strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); + strbuf_addch(out, '\n'); + goto cleanup; + } + + sub = submodule_from_path(null_sha1, ce->name); + + if (suc->recursive_prefix) + displaypath = relative_path(suc->recursive_prefix, + ce->name, &displaypath_sb); + else + displaypath = ce->name; + + if (suc->update.type == SM_UPDATE_NONE + || (suc->update.type == SM_UPDATE_UNSPECIFIED + && sub->update_strategy.type == SM_UPDATE_NONE)) { + strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); + strbuf_addch(out, '\n'); + goto cleanup; + } + + /* + * Looking up the url in .git/config. + * We must not fall back to .gitmodules as we only want + * to process configured submodules. + */ + strbuf_reset(&sb); + strbuf_addf(&sb, "submodule.%s.url", sub->name); + git_config_get_string(sb.buf, &url); + if (!url) { + /* + * Only mention uninitialized submodules when their + * path have been specified + */ + if (suc->warn_if_uninitialized) { + strbuf_addf(out, + _("Submodule path '%s' not initialized"), + displaypath); + strbuf_addch(out, '\n'); + strbuf_addstr(out, + _("Maybe you want to use 'update --init'?")); + strbuf_addch(out, '\n'); + } + goto cleanup; + } + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s/.git", ce->name); + needs_cloning = !file_exists(sb.buf); + + strbuf_reset(&sb); + strbuf_addf(&sb, "%06o %s %d %d\t%s\n", ce->ce_mode, + sha1_to_hex(ce->sha1), ce_stage(ce), + needs_cloning, ce->name); + string_list_append(&suc->projectlines, sb.buf); + + if (!needs_cloning) + goto cleanup; + + child->git_cmd = 1; + child->no_stdin = 1; + child->stdout_to_stderr = 1; + child->err = -1; + argv_array_push(&child->args, "submodule--helper"); + argv_array_push(&child->args, "clone"); + if (suc->quiet) + argv_array_push(&child->args, "--quiet"); + if (suc->prefix) + argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); + argv_array_pushl(&child->args, "--path", sub->path, NULL); + argv_array_pushl(&child->args, "--name", sub->name, NULL); + argv_array_pushl(&child->args, "--url", url, NULL); + if (suc->reference) + argv_array_push(&child->args, suc->reference); + if (suc->depth) + argv_array_push(&child->args, suc->depth); + +cleanup: + free(url); + strbuf_reset(&displaypath_sb); + strbuf_reset(&sb); + + return needs_cloning; +} + +static int update_clone_get_next_task(struct child_process *child, + struct strbuf *err, + void *suc_cb, + void **void_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + + for (; suc->current < suc->list.nr; suc->current++) { + const struct cache_entry *ce = suc->list.entries[suc->current]; + if (prepare_to_clone_next_submodule(ce, child, suc, err)) { + suc->current++; + return 1; + } + } + return 0; +} + +static int update_clone_start_failure(struct strbuf *err, + void *suc_cb, + void *void_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + suc->quickstop = 1; + return 1; +} + +static int update_clone_task_finished(int result, + struct strbuf *err, + void *suc_cb, + void *void_task_cb) +{ + struct submodule_update_clone *suc = suc_cb; + + if (!result) + return 0; + + suc->quickstop = 1; + return 1; +} + +static int update_clone(int argc, const char **argv, const char *prefix) +{ + const char *update = NULL; + int max_jobs = -1; + struct string_list_item *item; + struct pathspec pathspec; + struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; + + struct option module_update_clone_options[] = { + OPT_STRING(0, "prefix", &prefix, + N_("path"), + N_("path into the working tree")), + OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix, + N_("path"), + N_("path into the working tree, across nested " + "submodule boundaries")), + OPT_STRING(0, "update", &update, + N_("string"), + N_("rebase, merge, checkout or none")), + OPT_STRING(0, "reference", &suc.reference, N_("repo"), + N_("reference repository")), + OPT_STRING(0, "depth", &suc.depth, "<depth>", + N_("Create a shallow clone truncated to the " + "specified number of revisions")), + OPT_INTEGER('j', "jobs", &max_jobs, + N_("parallel jobs")), + OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"), + NULL + }; + suc.prefix = prefix; + + argc = parse_options(argc, argv, prefix, module_update_clone_options, + git_submodule_helper_usage, 0); + + if (update) + if (parse_submodule_update_strategy(update, &suc.update) < 0) + die(_("bad value for update parameter")); + + if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) + return 1; + + if (pathspec.nr) + suc.warn_if_uninitialized = 1; + + /* Overlay the parsed .gitmodules file with .git/config */ + gitmodules_config(); + git_config(submodule_config, NULL); + + if (max_jobs < 0) + max_jobs = parallel_submodules(); + + run_processes_parallel(max_jobs, + update_clone_get_next_task, + update_clone_start_failure, + update_clone_task_finished, + &suc); + + /* + * We saved the output and put it out all at once now. + * That means: + * - the listener does not have to interleave their (checkout) + * work with our fetching. The writes involved in a + * checkout involve more straightforward sequential I/O. + * - the listener can avoid doing any work if fetching failed. + */ + if (suc.quickstop) + return 1; + + for_each_string_list_item(item, &suc.projectlines) + utf8_fprintf(stdout, "%s", item->string); + + return 0; +} + struct cmd_struct { const char *cmd; int (*fn)(int, const char **, const char *); @@ -258,19 +578,21 @@ static struct cmd_struct commands[] = { {"list", module_list}, {"name", module_name}, {"clone", module_clone}, + {"sanitize-config", module_sanitize_config}, + {"update-clone", update_clone} }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) { int i; if (argc < 2) - die(_("fatal: submodule--helper subcommand must be " + die(_("submodule--helper subcommand must be " "called with a subcommand")); for (i = 0; i < ARRAY_SIZE(commands); i++) if (!strcmp(argv[1], commands[i].cmd)) return commands[i].fn(argc - 1, argv + 1, prefix); - die(_("fatal: '%s' is not a valid submodule--helper " + die(_("'%s' is not a valid submodule--helper " "subcommand"), argv[1]); } diff --git a/builtin/tag.c b/builtin/tag.c index 1705c94665..528a1bab69 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -29,6 +29,7 @@ static const char * const git_tag_usage[] = { }; static unsigned int colopts; +static int force_sign_annotate; static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, const char *format) { @@ -166,6 +167,11 @@ static int git_tag_config(const char *var, const char *value, void *cb) status = git_gpg_config(var, value, cb); if (status) return status; + if (!strcmp(var, "tag.forcesignannotated")) { + force_sign_annotate = git_config_bool(var, value); + return 0; + } + if (starts_with(var, "column.")) return git_column_config(var, value, "tag", &colopts); return git_default_config(var, value, cb); @@ -327,7 +333,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) char *cleanup_arg = NULL; int create_reflog = 0; int annotate = 0, force = 0; - int cmdmode = 0; + int cmdmode = 0, create_tag_object = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; struct ref_transaction *transaction; @@ -385,12 +391,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) opt.sign = 1; set_signing_key(keyid); } - if (opt.sign) - annotate = 1; + create_tag_object = (opt.sign || annotate || msg.given || msgfile); + if (argc == 0 && !cmdmode) cmdmode = 'l'; - if ((annotate || msg.given || msgfile || force) && (cmdmode != 0)) + if ((create_tag_object || force) && (cmdmode != 0)) usage_with_options(git_tag_usage, options); finalize_colopts(&colopts, -1); @@ -431,7 +437,6 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (msg.given || msgfile) { if (msg.given && msgfile) die(_("only one -F or -m option is allowed.")); - annotate = 1; if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { @@ -474,8 +479,11 @@ int cmd_tag(int argc, const char **argv, const char *prefix) else die(_("Invalid cleanup mode %s"), cleanup_arg); - if (annotate) + if (create_tag_object) { + if (force_sign_annotate && !annotate) + opt.sign = 1; create_tag(object, tag, &buf, &opt, prev, object); + } transaction = ref_transaction_begin(&err); if (!transaction || diff --git a/builtin/worktree.c b/builtin/worktree.c index 38b56096bd..d8e3795dc4 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -21,6 +21,7 @@ static const char * const worktree_usage[] = { struct add_opts { int force; int detach; + int checkout; const char *new_branch; int force_new_branch; }; @@ -284,18 +285,22 @@ static int add_worktree(const char *path, const char *refname, if (ret) goto done; - cp.argv = NULL; - argv_array_clear(&cp.args); - argv_array_pushl(&cp.args, "reset", "--hard", NULL); - cp.env = child_env.argv; - ret = run_command(&cp); - if (!ret) { - is_junk = 0; - free(junk_work_tree); - free(junk_git_dir); - junk_work_tree = NULL; - junk_git_dir = NULL; + if (opts->checkout) { + cp.argv = NULL; + argv_array_clear(&cp.args); + argv_array_pushl(&cp.args, "reset", "--hard", NULL); + cp.env = child_env.argv; + ret = run_command(&cp); + if (ret) + goto done; } + + is_junk = 0; + free(junk_work_tree); + free(junk_git_dir); + junk_work_tree = NULL; + junk_git_dir = NULL; + done: strbuf_reset(&sb); strbuf_addf(&sb, "%s/locked", sb_repo.buf); @@ -320,10 +325,12 @@ static int add(int ac, const char **av, const char *prefix) OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), + OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_END() }; memset(&opts, 0, sizeof(opts)); + opts.checkout = 1; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (!!opts.detach + !!opts.new_branch + !!new_branch_force > 1) die(_("-b, -B, and --detach are mutually exclusive")); @@ -435,12 +435,14 @@ int create_bundle(struct bundle_header *header, const char *path, /* write prerequisites */ if (compute_and_write_prerequisites(bundle_fd, &revs, argc, argv)) - return -1; + goto err; argc = setup_revisions(argc, argv, &revs, NULL); - if (argc > 1) - return error(_("unrecognized argument: %s"), argv[1]); + if (argc > 1) { + error(_("unrecognized argument: %s"), argv[1]); + goto err; + } object_array_remove_duplicates(&revs.pending); @@ -448,17 +450,26 @@ int create_bundle(struct bundle_header *header, const char *path, if (!ref_count) die(_("Refusing to create empty bundle.")); else if (ref_count < 0) - return -1; + goto err; /* write pack */ - if (write_pack_data(bundle_fd, &revs)) - return -1; + if (write_pack_data(bundle_fd, &revs)) { + bundle_fd = -1; /* already closed by the above call */ + goto err; + } if (!bundle_to_stdout) { if (commit_lock_file(&lock)) die_errno(_("cannot create '%s'"), path); } return 0; +err: + if (!bundle_to_stdout) { + if (0 <= bundle_fd) + close(bundle_fd); + rollback_lock_file(&lock); + } + return -1; } int unbundle(struct bundle_header *header, int bundle_fd, int flags) @@ -651,7 +651,6 @@ extern int prefer_symlink_refs; extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int warn_on_object_refname_ambiguity; -extern int shared_repository; extern const char *apply_default_whitespace; extern const char *apply_default_ignorewhitespace; extern const char *git_attributes_file; @@ -664,6 +663,9 @@ extern size_t delta_base_cache_limit; extern unsigned long big_file_threshold; extern unsigned long pack_size_limit_cfg; +void set_shared_repository(int value); +int get_shared_repository(void); + /* * Do replace refs need to be checked this run? This variable is * initialized to true unless --no-replace-object is used or @@ -745,9 +747,39 @@ extern int grafts_replace_parents; */ #define GIT_REPO_VERSION 0 #define GIT_REPO_VERSION_READ 1 -extern int repository_format_version; extern int repository_format_precious_objects; -extern int check_repository_format(void); + +struct repository_format { + int version; + int precious_objects; + int is_bare; + char *work_tree; + struct string_list unknown_extensions; +}; + +/* + * Read the repository format characteristics from the config file "path" into + * "format" struct. Returns the numeric version. On error, -1 is returned, + * format->version is set to -1, and all other fields in the struct are + * undefined. + */ +int read_repository_format(struct repository_format *format, const char *path); + +/* + * Verify that the repository described by repository_format is something we + * can read. If it is, return 0. Otherwise, return -1, and "err" will describe + * any errors encountered. + */ +int verify_repository_format(const struct repository_format *format, + struct strbuf *err); + +/* + * Check the repository format version in the path found in get_git_dir(), + * and die if it is a version we don't understand. Generally one would + * set_git_dir() before calling this, and use it only for "are we in a valid + * repo?". + */ +extern void check_repository_format(void); #define MTIME_CHANGED 0x0001 #define CTIME_CHANGED 0x0002 @@ -1526,7 +1558,6 @@ extern void git_config(config_fn_t fn, void *); extern int git_config_with_options(config_fn_t fn, void *, struct git_config_source *config_source, int respect_includes); -extern int git_config_early(config_fn_t fn, void *, const char *repo_config); extern int git_parse_ulong(const char *, unsigned long *); extern int git_parse_maybe_bool(const char *); extern int git_config_int(const char *, const char *); @@ -1550,7 +1581,6 @@ extern void git_config_set_multivar_in_file(const char *, const char *, const ch extern int git_config_rename_section(const char *, const char *); extern int git_config_rename_section_in_file(const char *, const char *, const char *); extern const char *git_etc_gitconfig(void); -extern int check_repository_format_version(const char *var, const char *value, void *cb); extern int git_env_bool(const char *, int); extern unsigned long git_env_ulong(const char *, unsigned long); extern int git_config_system(void); @@ -147,6 +147,7 @@ struct pretty_print_context { int preserve_subject; struct date_mode date_mode; unsigned date_mode_explicit:1; + int expand_tabs_in_log; int need_8bit_cte; char *notes_message; struct reflog_walk_info *reflog_info; diff --git a/compat/mingw.c b/compat/mingw.c index 54c82ecf20..0413d5c3cd 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -763,15 +763,12 @@ struct tm *localtime_r(const time_t *timep, struct tm *result) char *mingw_getcwd(char *pointer, int len) { - int i; wchar_t wpointer[MAX_PATH]; if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer))) return NULL; if (xwcstoutf(pointer, wpointer, len) < 0) return NULL; - for (i = 0; pointer[i]; i++) - if (pointer[i] == '\\') - pointer[i] = '/'; + convert_slashes(pointer); return pointer; } @@ -2112,9 +2109,7 @@ static void setup_windows_environment() * executable (by not mistaking the dir separators * for escape characters). */ - for (; *tmp; tmp++) - if (*tmp == '\\') - *tmp = '/'; + convert_slashes(tmp); } /* simulate TERM to enable auto-color (see color.c) */ diff --git a/compat/mingw.h b/compat/mingw.h index c008694639..1de70ffd62 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -406,7 +406,7 @@ static inline void convert_slashes(char *path) int mingw_offset_1st_component(const char *path); #define offset_1st_component mingw_offset_1st_component #define PATH_SEP ';' -#ifndef __MINGW64_VERSION_MAJOR +#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800) #define PRIuMAX "I64u" #define PRId64 "I64d" #else diff --git a/compat/snprintf.c b/compat/snprintf.c index 42ea1ac110..0b11688537 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -9,7 +9,7 @@ * always have room for a trailing NUL byte. */ #ifndef SNPRINTF_SIZE_CORR -#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) +#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) && (!defined(_MSC_VER) || _MSC_VER < 1900) #define SNPRINTF_SIZE_CORR 1 #else #define SNPRINTF_SIZE_CORR 0 diff --git a/compat/vcbuild/include/unistd.h b/compat/vcbuild/include/unistd.h index c65c2cd566..3a959d124c 100644 --- a/compat/vcbuild/include/unistd.h +++ b/compat/vcbuild/include/unistd.h @@ -45,11 +45,15 @@ typedef unsigned long long uintmax_t; typedef int64_t off64_t; +#if !defined(_MSC_VER) || _MSC_VER < 1600 #define INTMAX_MIN _I64_MIN #define INTMAX_MAX _I64_MAX #define UINTMAX_MAX _UI64_MAX #define UINT32_MAX 0xffffffff /* 4294967295U */ +#else +#include <stdint.h> +#endif #define STDIN_FILENO 0 #define STDOUT_FILENO 1 @@ -162,7 +162,7 @@ void git_config_push_parameter(const char *text) { struct strbuf env = STRBUF_INIT; const char *old = getenv(CONFIG_DATA_ENVIRONMENT); - if (old) { + if (old && *old) { strbuf_addstr(&env, old); strbuf_addch(&env, ' '); } @@ -1188,11 +1188,12 @@ int git_config_system(void) return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); } -int git_config_early(config_fn_t fn, void *data, const char *repo_config) +static int do_git_config_sequence(config_fn_t fn, void *data) { int ret = 0, found = 0; char *xdg_config = xdg_config_home("config"); char *user_config = expand_user_path("~/.gitconfig"); + char *repo_config = git_pathdup("config"); if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) { ret += git_config_from_file(fn, git_etc_gitconfig(), @@ -1228,6 +1229,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) free(xdg_config); free(user_config); + free(repo_config); return ret == 0 ? found : ret; } @@ -1235,8 +1237,6 @@ int git_config_with_options(config_fn_t fn, void *data, struct git_config_source *config_source, int respect_includes) { - char *repo_config = NULL; - int ret; struct config_include_data inc = CONFIG_INCLUDE_INIT; if (respect_includes) { @@ -1257,11 +1257,7 @@ int git_config_with_options(config_fn_t fn, void *data, else if (config_source && config_source->blob) return git_config_from_blob_ref(fn, config_source->blob, data); - repo_config = git_pathdup("config"); - ret = git_config_early(fn, data, repo_config); - if (repo_config) - free(repo_config); - return ret; + return do_git_config_sequence(fn, data); } static void git_config_raw(config_fn_t fn, void *data) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e3918c87e3..34024754d9 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1339,15 +1339,15 @@ _git_help () { case "$cur" in --*) - __gitcomp "--all --info --man --web" + __gitcomp "--all --guides --info --man --web" return ;; esac __git_compute_all_commands __gitcomp "$__git_all_commands $(__git_aliases) attributes cli core-tutorial cvs-migration - diffcore gitk glossary hooks ignore modules - namespaces repository-layout tutorial tutorial-2 + diffcore everyday gitk glossary hooks ignore modules + namespaces repository-layout revisions tutorial tutorial-2 workflows " } @@ -1458,6 +1458,7 @@ _git_log () --relative-date --date= --pretty= --format= --oneline --show-signature + --cherry-mark --cherry-pick --graph --decorate --decorate= diff --git a/credential-cache.c b/credential-cache.c index f4afdc6988..86e21de49b 100644 --- a/credential-cache.c +++ b/credential-cache.c @@ -32,6 +32,7 @@ static int send_request(const char *socket, const struct strbuf *out) write_or_die(1, in, r); got_data = 1; } + close(fd); return got_data; } diff --git a/credential.c b/credential.c index 7d6501d190..aa996669fc 100644 --- a/credential.c +++ b/credential.c @@ -63,9 +63,12 @@ static int credential_config_callback(const char *var, const char *value, key = dot + 1; } - if (!strcmp(key, "helper")) - string_list_append(&c->helpers, value); - else if (!strcmp(key, "username")) { + if (!strcmp(key, "helper")) { + if (*value) + string_list_append(&c->helpers, value); + else + string_list_clear(&c->helpers, 0); + } else if (!strcmp(key, "username")) { if (!c->username) c->username = xstrdup(value); } @@ -168,6 +168,11 @@ long parse_algorithm_value(const char *value) * never be affected by the setting of diff.renames * the user happens to have in the configuration file. */ +void init_diff_ui_defaults(void) +{ + diff_detect_rename_default = 1; +} + int git_diff_ui_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { @@ -266,6 +266,7 @@ extern int parse_long_opt(const char *opt, const char **argv, const char **optarg); extern int git_diff_basic_config(const char *var, const char *value, void *cb); +extern void init_diff_ui_defaults(void); extern int git_diff_ui_config(const char *var, const char *value, void *cb); extern void diff_setup(struct diff_options *); extern int diff_opt_parse(struct diff_options *, const char **, int, const char *); diff --git a/diffcore-rename.c b/diffcore-rename.c index 3b3c1ed535..7f03eb5a04 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -340,9 +340,11 @@ static int find_exact_renames(struct diff_options *options) int i, renames = 0; struct hashmap file_table; - /* Add all sources to the hash table */ + /* Add all sources to the hash table in reverse order, because + * later on they will be retrieved in LIFO order. + */ hashmap_init(&file_table, NULL, rename_src_nr); - for (i = 0; i < rename_src_nr; i++) + for (i = rename_src_nr-1; i >= 0; i--) insert_file_table(&file_table, i, rename_src[i].p->one); /* Walk the destinations and find best source match */ diff --git a/environment.c b/environment.c index 6cc0a7780f..57acb2fe2a 100644 --- a/environment.c +++ b/environment.c @@ -25,11 +25,9 @@ int log_all_ref_updates = -1; /* unspecified */ int warn_ambiguous_refs = 1; int warn_on_object_refname_ambiguity = 1; int ref_paranoia = -1; -int repository_format_version; int repository_format_precious_objects; const char *git_commit_encoding; const char *git_log_output_encoding; -int shared_repository = PERM_UMASK; const char *apply_default_whitespace; const char *apply_default_ignorewhitespace; const char *git_attributes_file; @@ -324,3 +322,24 @@ const char *get_commit_output_encoding(void) { return git_commit_encoding ? git_commit_encoding : "UTF-8"; } + +static int the_shared_repository = PERM_UMASK; +static int need_shared_repository_from_config = 1; + +void set_shared_repository(int value) +{ + the_shared_repository = value; + need_shared_repository_from_config = 0; +} + +int get_shared_repository(void) +{ + if (need_shared_repository_from_config) { + const char *var = "core.sharedrepository"; + const char *value; + if (!git_config_get_value(var, &value)) + the_shared_repository = git_config_perm(var, value); + need_shared_repository_from_config = 0; + } + return the_shared_repository; +} diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 77876d433a..822f857038 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -45,6 +45,7 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); my $diff_algorithm = $repo->config('diff.algorithm'); +my $diff_filter = $repo->config('interactive.difffilter'); my $use_readkey = 0; my $use_termcap = 0; @@ -754,7 +755,14 @@ sub parse_diff { my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); + my @display_cmd = ("git", @diff_cmd, qw(--color --), $path); + if (defined $diff_filter) { + # quotemeta is overkill, but sufficient for shell-quoting + my $diff = join(' ', map { quotemeta } @display_cmd); + @display_cmd = ("$diff | $diff_filter"); + } + + @colored = run_cmd_pipe(@display_cmd); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -765,7 +773,7 @@ sub parse_diff { } push @{$hunk[-1]{TEXT}}, $diff[$i]; push @{$hunk[-1]{DISPLAY}}, - ($diff_use_color ? $colored[$i] : $diff[$i]); + (@colored ? $colored[$i] : $diff[$i]); } return @hunk; } diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 54ac8e4846..302c56de5b 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -372,3 +372,28 @@ get_merge_tool () { fi echo "$merge_tool" } + +mergetool_find_win32_cmd () { + executable=$1 + sub_directory=$2 + + # Use $executable if it exists in $PATH + if type -p "$executable" >/dev/null 2>&1 + then + printf '%s' "$executable" + return + fi + + # Look for executable in the typical locations + for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' | + cut -d '=' -f 2- | sort -u) + do + if test -n "$directory" && test -x "$directory/$sub_directory/$executable" + then + printf '%s' "$directory/$sub_directory/$executable" + return + fi + done + + printf '%s' "$executable" +} @@ -1160,6 +1160,15 @@ class P4UserMap: self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" self.emails[output["Email"]] = output["User"] + mapUserConfigRegex = re.compile(r"^\s*(\S+)\s*=\s*(.+)\s*<(\S+)>\s*$", re.VERBOSE) + for mapUserConfig in gitConfigList("git-p4.mapUser"): + mapUser = mapUserConfigRegex.findall(mapUserConfig) + if mapUser and len(mapUser[0]) == 3: + user = mapUser[0][0] + fullname = mapUser[0][1] + email = mapUser[0][2] + self.users[user] = fullname + " <" + email + ">" + self.emails[email] = user s = '' for (key, val) in self.users.items(): diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 4cde685b43..9ea30756f1 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -548,7 +548,8 @@ do_next () { mark_action_done do_pick $sha1 "$rest" - warn "Stopped at $sha1... $rest" + sha1_abbrev=$(git rev-parse --short $sha1) + warn "Stopped at $sha1_abbrev... $rest" exit_with_patch $sha1 0 ;; squash|s|fixup|f) diff --git a/git-rebase.sh b/git-rebase.sh index cf60c43908..0bf41ee72b 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -248,6 +248,7 @@ do ;; --exec=*) cmd="${cmd}exec ${1#--exec=}${LF}" + test -z "$interactive_rebase" && interactive_rebase=implied ;; --interactive) interactive_rebase=explicit @@ -348,12 +349,6 @@ do done test $# -gt 2 && usage -if test -n "$cmd" && - test "$interactive_rebase" != explicit -then - die "$(gettext "The --exec option must be used with the --interactive option")" -fi - if test -n "$action" then test -z "$in_progress" && die "$(gettext "No rebase in progress?")" diff --git a/git-submodule.sh b/git-submodule.sh index 43c68deee9..cd749f473c 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -192,6 +192,16 @@ isnumber() n=$(($1 + 0)) 2>/dev/null && test "$n" = "$1" } +# Sanitize the local git environment for use within a submodule. We +# can't simply use clear_local_git_env since we want to preserve some +# of the settings from GIT_CONFIG_PARAMETERS. +sanitize_submodule_env() +{ + sanitized_config=$(git submodule--helper sanitize-config) + clear_local_git_env + GIT_CONFIG_PARAMETERS=$sanitized_config +} + # # Add a new submodule to the working tree, .gitmodules and the index # @@ -347,9 +357,9 @@ Use -f if you really want to add it." >&2 echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")" fi fi - git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" "$reference" "$depth" || exit + git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit ( - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && # ash fails to wordsplit ${branch:+-b "$branch"...} case "$branch" in @@ -418,7 +428,7 @@ cmd_foreach() name=$(git submodule--helper name "$sm_path") ( prefix="$prefix$sm_path/" - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && sm_path=$(relative_path "$sm_path") && # we make $path available to scripts ... @@ -592,14 +602,14 @@ cmd_deinit() } is_tip_reachable () ( - clear_local_git_env + sanitize_submodule_env && cd "$1" && rev=$(git rev-list -n 1 "$2" --not --all 2>/dev/null) && test -z "$rev" ) fetch_in_submodule () ( - clear_local_git_env + sanitize_submodule_env && cd "$1" && case "$2" in '') @@ -663,6 +673,14 @@ cmd_update() --depth=*) depth=$1 ;; + -j|--jobs) + case "$2" in '') usage ;; esac + jobs="--jobs=$2" + shift + ;; + --jobs=*) + jobs=$1 + ;; --) shift break @@ -682,17 +700,21 @@ cmd_update() cmd_init "--" "$@" || return fi - cloned_modules= - git submodule--helper list --prefix "$wt_prefix" "$@" | { + { + git submodule--helper update-clone ${GIT_QUIET:+--quiet} \ + ${wt_prefix:+--prefix "$wt_prefix"} \ + ${prefix:+--recursive-prefix "$prefix"} \ + ${update:+--update "$update"} \ + ${reference:+--reference "$reference"} \ + ${depth:+--depth "$depth"} \ + ${jobs:+$jobs} \ + "$@" || echo "#unmatched" + } | { err= - while read mode sha1 stage sm_path + while read mode sha1 stage just_cloned sm_path do die_if_unmatched "$mode" - if test "$stage" = U - then - echo >&2 "Skipping unmerged submodule $prefix$sm_path" - continue - fi + name=$(git submodule--helper name "$sm_path") || exit url=$(git config submodule."$name".url) branch=$(get_submodule_config "$name" branch master) @@ -709,29 +731,12 @@ cmd_update() displaypath=$(relative_path "$prefix$sm_path") - if test "$update_module" = "none" - then - echo "Skipping submodule '$displaypath'" - continue - fi - - if test -z "$url" + if test $just_cloned -eq 1 then - # Only mention uninitialized submodules when its - # path have been specified - test "$#" != "0" && - say "$(eval_gettext "Submodule path '\$displaypath' not initialized -Maybe you want to use 'update --init'?")" - continue - fi - - if ! test -d "$sm_path"/.git && ! test -f "$sm_path"/.git - then - git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$prefix" --path "$sm_path" --name "$name" --url "$url" "$reference" "$depth" || exit - cloned_modules="$cloned_modules;$name" subsha1= + update_module=checkout else - subsha1=$(clear_local_git_env; cd "$sm_path" && + subsha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) || die "$(eval_gettext "Unable to find current revision in submodule path '\$displaypath'")" fi @@ -741,11 +746,11 @@ Maybe you want to use 'update --init'?")" if test -z "$nofetch" then # Fetch remote before determining tracking $sha1 - (clear_local_git_env; cd "$sm_path" && git-fetch) || + (sanitize_submodule_env; cd "$sm_path" && git-fetch) || die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")" fi - remote_name=$(clear_local_git_env; cd "$sm_path" && get_default_remote) - sha1=$(clear_local_git_env; cd "$sm_path" && + remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote) + sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify "${remote_name}/${branch}") || die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")" fi @@ -774,13 +779,6 @@ Maybe you want to use 'update --init'?")" die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")" fi - # Is this something we just cloned? - case ";$cloned_modules;" in - *";$name;"*) - # then there is no local change to integrate - update_module=checkout ;; - esac - must_die_on_failure= case "$update_module" in checkout) @@ -810,7 +808,7 @@ Maybe you want to use 'update --init'?")" die "$(eval_gettext "Invalid update mode '$update_module' for submodule '$name'")" esac - if (clear_local_git_env; cd "$sm_path" && $command "$sha1") + if (sanitize_submodule_env; cd "$sm_path" && $command "$sha1") then say "$say_msg" elif test -n "$must_die_on_failure" @@ -826,7 +824,7 @@ Maybe you want to use 'update --init'?")" then ( prefix="$prefix$sm_path/" - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && eval cmd_update ) @@ -864,7 +862,7 @@ Maybe you want to use 'update --init'?")" set_name_rev () { revname=$( ( - clear_local_git_env + sanitize_submodule_env cd "$1" && { git describe "$2" 2>/dev/null || git describe --tags "$2" 2>/dev/null || @@ -1148,7 +1146,7 @@ cmd_status() else if test -z "$cached" then - sha1=$(clear_local_git_env; cd "$sm_path" && git rev-parse --verify HEAD) + sha1=$(sanitize_submodule_env; cd "$sm_path" && git rev-parse --verify HEAD) fi set_name_rev "$sm_path" "$sha1" say "+$sha1 $displaypath$revname" @@ -1158,7 +1156,7 @@ cmd_status() then ( prefix="$displaypath/" - clear_local_git_env + sanitize_submodule_env cd "$sm_path" && eval cmd_status ) || @@ -1232,7 +1230,7 @@ cmd_sync() if test -e "$sm_path"/.git then ( - clear_local_git_env + sanitize_submodule_env cd "$sm_path" remote=$(get_default_remote) git config remote."$remote".url "$sub_origin_url" diff --git a/log-tree.c b/log-tree.c index 60f983934d..78a5381d0e 100644 --- a/log-tree.c +++ b/log-tree.c @@ -683,6 +683,7 @@ void show_log(struct rev_info *opt) ctx.fmt = opt->commit_format; ctx.mailmap = opt->mailmap; ctx.color = opt->diffopt.use_color; + ctx.expand_tabs_in_log = opt->expand_tabs_in_log; ctx.output_encoding = get_log_output_encoding(); if (opt->from_ident.mail_begin && opt->from_ident.name_begin) ctx.from_ident = &opt->from_ident; diff --git a/mergetools/examdiff b/mergetools/examdiff new file mode 100644 index 0000000000..7b524d4088 --- /dev/null +++ b/mergetools/examdiff @@ -0,0 +1,18 @@ +diff_cmd () { + "$merge_tool_path" "$LOCAL" "$REMOTE" -nh +} + +merge_cmd () { + touch "$BACKUP" + if $base_present + then + "$merge_tool_path" -merge "$LOCAL" "$BASE" "$REMOTE" -o:"$MERGED" -nh + else + "$merge_tool_path" -merge "$LOCAL" "$REMOTE" -o:"$MERGED" -nh + fi + check_unchanged +} + +translate_merge_tool_path() { + mergetool_find_win32_cmd "ExamDiff.com" "ExamDiff Pro" +} diff --git a/mergetools/winmerge b/mergetools/winmerge index 74a66d4e8d..f3819d316a 100644 --- a/mergetools/winmerge +++ b/mergetools/winmerge @@ -13,24 +13,5 @@ merge_cmd () { } translate_merge_tool_path() { - # Use WinMergeU.exe if it exists in $PATH - if type -p WinMergeU.exe >/dev/null 2>&1 - then - printf WinMergeU.exe - return - fi - - # Look for WinMergeU.exe in the typical locations - winmerge_exe="WinMerge/WinMergeU.exe" - for directory in $(env | grep -Ei '^PROGRAM(FILES(\(X86\))?|W6432)=' | - cut -d '=' -f 2- | sort -u) - do - if test -n "$directory" && test -x "$directory/$winmerge_exe" - then - printf '%s' "$directory/$winmerge_exe" - return - fi - done - - printf WinMergeU.exe + mergetool_find_win32_cmd "WinMergeU.exe" "WinMerge" } @@ -702,17 +702,17 @@ static int calc_shared_perm(int mode) { int tweak; - if (shared_repository < 0) - tweak = -shared_repository; + if (get_shared_repository() < 0) + tweak = -get_shared_repository(); else - tweak = shared_repository; + tweak = get_shared_repository(); if (!(mode & S_IWUSR)) tweak &= ~0222; if (mode & S_IXUSR) /* Copy read bits to execute bits */ tweak |= (tweak & 0444) >> 2; - if (shared_repository < 0) + if (get_shared_repository() < 0) mode = (mode & ~0777) | tweak; else mode |= tweak; @@ -725,7 +725,7 @@ int adjust_shared_perm(const char *path) { int old_mode, new_mode; - if (!shared_repository) + if (!get_shared_repository()) return 0; if (get_st_mode_bits(path, &old_mode) < 0) return -1; @@ -16,6 +16,7 @@ static struct cmt_fmt_map { const char *name; enum cmit_fmt format; int is_tformat; + int expand_tabs_in_log; int is_alias; const char *user_format; } *commit_formats; @@ -87,13 +88,13 @@ static int git_pretty_formats_config(const char *var, const char *value, void *c static void setup_commit_formats(void) { struct cmt_fmt_map builtin_formats[] = { - { "raw", CMIT_FMT_RAW, 0 }, - { "medium", CMIT_FMT_MEDIUM, 0 }, - { "short", CMIT_FMT_SHORT, 0 }, - { "email", CMIT_FMT_EMAIL, 0 }, - { "fuller", CMIT_FMT_FULLER, 0 }, - { "full", CMIT_FMT_FULL, 0 }, - { "oneline", CMIT_FMT_ONELINE, 1 } + { "raw", CMIT_FMT_RAW, 0, 0 }, + { "medium", CMIT_FMT_MEDIUM, 0, 8 }, + { "short", CMIT_FMT_SHORT, 0, 0 }, + { "email", CMIT_FMT_EMAIL, 0, 0 }, + { "fuller", CMIT_FMT_FULLER, 0, 8 }, + { "full", CMIT_FMT_FULL, 0, 8 }, + { "oneline", CMIT_FMT_ONELINE, 1, 0 } }; commit_formats_len = ARRAY_SIZE(builtin_formats); builtin_formats_len = commit_formats_len; @@ -172,6 +173,7 @@ void get_commit_format(const char *arg, struct rev_info *rev) rev->commit_format = commit_format->format; rev->use_terminator = commit_format->is_tformat; + rev->expand_tabs_in_log_default = commit_format->expand_tabs_in_log; if (commit_format->format == CMIT_FMT_USERFORMAT) { save_user_format(rev, commit_format->user_format, commit_format->is_tformat); @@ -1629,6 +1631,72 @@ void pp_title_line(struct pretty_print_context *pp, strbuf_release(&title); } +static int pp_utf8_width(const char *start, const char *end) +{ + int width = 0; + size_t remain = end - start; + + while (remain) { + int n = utf8_width(&start, &remain); + if (n < 0 || !start) + return -1; + width += n; + } + return width; +} + +static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth, + const char *line, int linelen) +{ + const char *tab; + + while ((tab = memchr(line, '\t', linelen)) != NULL) { + int width = pp_utf8_width(line, tab); + + /* + * If it wasn't well-formed utf8, or it + * had characters with badly defined + * width (control characters etc), just + * give up on trying to align things. + */ + if (width < 0) + break; + + /* Output the data .. */ + strbuf_add(sb, line, tab - line); + + /* .. and the de-tabified tab */ + strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth)); + + /* Skip over the printed part .. */ + linelen -= tab + 1 - line; + line = tab + 1; + } + + /* + * Print out everything after the last tab without + * worrying about width - there's nothing more to + * align. + */ + strbuf_add(sb, line, linelen); +} + +/* + * pp_handle_indent() prints out the intendation, and + * the whole line (without the final newline), after + * de-tabifying. + */ +static void pp_handle_indent(struct pretty_print_context *pp, + struct strbuf *sb, int indent, + const char *line, int linelen) +{ + strbuf_addchars(sb, ' ', indent); + if (pp->expand_tabs_in_log) + strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, line, linelen); + else + strbuf_add(sb, line, linelen); +} + void pp_remainder(struct pretty_print_context *pp, const char **msg_p, struct strbuf *sb, @@ -1653,8 +1721,12 @@ void pp_remainder(struct pretty_print_context *pp, strbuf_grow(sb, linelen + indent + 20); if (indent) - strbuf_addchars(sb, ' ', indent); - strbuf_add(sb, line, linelen); + pp_handle_indent(pp, sb, indent, line, linelen); + else if (pp->expand_tabs_in_log) + strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, + line, linelen); + else + strbuf_add(sb, line, linelen); strbuf_addch(sb, '\n'); } } @@ -43,6 +43,19 @@ void sq_quote_buf(struct strbuf *dst, const char *src) free(to_free); } +void sq_quotef(struct strbuf *dst, const char *fmt, ...) +{ + struct strbuf src = STRBUF_INIT; + + va_list ap; + va_start(ap, fmt); + strbuf_vaddf(&src, fmt, ap); + va_end(ap); + + sq_quote_buf(dst, src.buf); + strbuf_release(&src); +} + void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) { int i; @@ -25,10 +25,13 @@ struct strbuf; * sq_quote_buf() writes to an existing buffer of specified size; it * will return the number of characters that would have been written * excluding the final null regardless of the buffer size. + * + * sq_quotef() quotes the entire formatted string as a single result. */ extern void sq_quote_buf(struct strbuf *, const char *src); extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen); +extern void sq_quotef(struct strbuf *, const char *fmt, ...); /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have diff --git a/revision.c b/revision.c index 8b2dfe3160..b683476b9c 100644 --- a/revision.c +++ b/revision.c @@ -1356,8 +1356,10 @@ void init_revisions(struct rev_info *revs, const char *prefix) revs->skip_count = -1; revs->max_count = -1; revs->max_parents = -1; + revs->expand_tabs_in_log = -1; revs->commit_format = CMIT_FMT_DEFAULT; + revs->expand_tabs_in_log_default = 8; init_grep_defaults(); grep_init(&revs->grep_filter, prefix); @@ -1854,6 +1856,15 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->verbose_header = 1; revs->pretty_given = 1; get_commit_format(arg+9, revs); + } else if (!strcmp(arg, "--expand-tabs")) { + revs->expand_tabs_in_log = 8; + } else if (!strcmp(arg, "--no-expand-tabs")) { + revs->expand_tabs_in_log = 0; + } else if (skip_prefix(arg, "--expand-tabs=", &arg)) { + int val; + if (strtol_i(arg, 10, &val) < 0 || val < 0) + die("'%s': not a non-negative integer", arg); + revs->expand_tabs_in_log = val; } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) { revs->show_notes = 1; revs->show_notes_given = 1; @@ -2327,6 +2338,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s if (revs->first_parent_only && revs->bisect) die(_("--first-parent is incompatible with --bisect")); + if (revs->expand_tabs_in_log < 0) + revs->expand_tabs_in_log = revs->expand_tabs_in_log_default; + return left; } diff --git a/revision.h b/revision.h index dca0d38171..9fac1a607d 100644 --- a/revision.h +++ b/revision.h @@ -148,6 +148,8 @@ struct rev_info { linear:1; struct date_mode date_mode; + int expand_tabs_in_log; /* unset if negative */ + int expand_tabs_in_log_default; unsigned int abbrev; enum cmit_fmt commit_format; diff --git a/run-command.c b/run-command.c index c72601056c..8c7115ade4 100644 --- a/run-command.c +++ b/run-command.c @@ -902,7 +902,7 @@ struct parallel_processes { struct strbuf buffered_output; /* of finished children */ }; -static int default_start_failure(struct strbuf *err, +static int default_start_failure(struct strbuf *out, void *pp_cb, void *pp_task_cb) { @@ -910,7 +910,7 @@ static int default_start_failure(struct strbuf *err, } static int default_task_finished(int result, - struct strbuf *err, + struct strbuf *out, void *pp_cb, void *pp_task_cb) { @@ -994,7 +994,7 @@ static void pp_cleanup(struct parallel_processes *pp) * When get_next_task added messages to the buffer in its last * iteration, the buffered output is non empty. */ - fputs(pp->buffered_output.buf, stderr); + strbuf_write(&pp->buffered_output, stderr); strbuf_release(&pp->buffered_output); sigchain_pop_common(); @@ -1079,7 +1079,7 @@ static void pp_output(struct parallel_processes *pp) int i = pp->output_owner; if (pp->children[i].state == GIT_CP_WORKING && pp->children[i].err.len) { - fputs(pp->children[i].err.buf, stderr); + strbuf_write(&pp->children[i].err, stderr); strbuf_reset(&pp->children[i].err); } } @@ -1117,11 +1117,11 @@ static int pp_collect_finished(struct parallel_processes *pp) strbuf_addbuf(&pp->buffered_output, &pp->children[i].err); strbuf_reset(&pp->children[i].err); } else { - fputs(pp->children[i].err.buf, stderr); + strbuf_write(&pp->children[i].err, stderr); strbuf_reset(&pp->children[i].err); /* Output all other finished child processes */ - fputs(pp->buffered_output.buf, stderr); + strbuf_write(&pp->buffered_output, stderr); strbuf_reset(&pp->buffered_output); /* diff --git a/run-command.h b/run-command.h index 3d1e59e26e..de1727efab 100644 --- a/run-command.h +++ b/run-command.h @@ -140,7 +140,7 @@ void NORETURN async_exit(int code); * return the negative signal number. */ typedef int (*get_next_task_fn)(struct child_process *cp, - struct strbuf *err, + struct strbuf *out, void *pp_cb, void **pp_task_cb); @@ -149,7 +149,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp, * a new process. * * You must not write to stdout or stderr in this function. Add your - * message to the strbuf err instead, which will be printed without + * message to the strbuf out instead, which will be printed without * messing up the output of the other parallel processes. * * pp_cb is the callback cookie as passed into run_processes_parallel, @@ -159,7 +159,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp, * To send a signal to other child processes for abortion, return * the negative signal number. */ -typedef int (*start_failure_fn)(struct strbuf *err, +typedef int (*start_failure_fn)(struct strbuf *out, void *pp_cb, void *pp_task_cb); @@ -167,7 +167,7 @@ typedef int (*start_failure_fn)(struct strbuf *err, * This callback is called on every child process that finished processing. * * You must not write to stdout or stderr in this function. Add your - * message to the strbuf err instead, which will be printed without + * message to the strbuf out instead, which will be printed without * messing up the output of the other parallel processes. * * pp_cb is the callback cookie as passed into run_processes_parallel, @@ -178,7 +178,7 @@ typedef int (*start_failure_fn)(struct strbuf *err, * the negative signal number. */ typedef int (*task_finished_fn)(int result, - struct strbuf *err, + struct strbuf *out, void *pp_cb, void *pp_task_cb); @@ -5,7 +5,6 @@ static int inside_git_dir = -1; static int inside_work_tree = -1; static int work_tree_config_is_bogus; -static struct string_list unknown_extensions = STRING_LIST_INIT_DUP; static struct startup_info the_startup_info; struct startup_info *startup_info = &the_startup_info; @@ -373,14 +372,13 @@ void setup_work_tree(void) initialized = 1; } -static int check_repo_format(const char *var, const char *value, void *cb) +static int check_repo_format(const char *var, const char *value, void *vdata) { + struct repository_format *data = vdata; const char *ext; if (strcmp(var, "core.repositoryformatversion") == 0) - repository_format_version = git_config_int(var, value); - else if (strcmp(var, "core.sharedrepository") == 0) - shared_repository = git_config_perm(var, value); + data->version = git_config_int(var, value); else if (skip_prefix(var, "extensions.", &ext)) { /* * record any known extensions here; otherwise, @@ -390,9 +388,15 @@ static int check_repo_format(const char *var, const char *value, void *cb) if (!strcmp(ext, "noop")) ; else if (!strcmp(ext, "preciousobjects")) - repository_format_precious_objects = git_config_bool(var, value); + data->precious_objects = git_config_bool(var, value); else - string_list_append(&unknown_extensions, ext); + string_list_append(&data->unknown_extensions, ext); + } else if (strcmp(var, "core.bare") == 0) { + data->is_bare = git_config_bool(var, value); + } else if (strcmp(var, "core.worktree") == 0) { + if (!value) + return config_error_nonbool(var); + data->work_tree = xstrdup(value); } return 0; } @@ -400,56 +404,84 @@ static int check_repo_format(const char *var, const char *value, void *cb) static int check_repository_format_gently(const char *gitdir, int *nongit_ok) { struct strbuf sb = STRBUF_INIT; - const char *repo_config; - config_fn_t fn; - int ret = 0; - - string_list_clear(&unknown_extensions, 0); + struct strbuf err = STRBUF_INIT; + struct repository_format candidate; + int has_common; - if (get_common_dir(&sb, gitdir)) - fn = check_repo_format; - else - fn = check_repository_format_version; + has_common = get_common_dir(&sb, gitdir); strbuf_addstr(&sb, "/config"); - repo_config = sb.buf; + read_repository_format(&candidate, sb.buf); + strbuf_release(&sb); /* - * git_config() can't be used here because it calls git_pathdup() - * to get $GIT_CONFIG/config. That call will make setup_git_env() - * set git_dir to ".git". - * - * We are in gitdir setup, no git dir has been found useable yet. - * Use a gentler version of git_config() to check if this repo - * is a good one. + * For historical use of check_repository_format() in git-init, + * we treat a missing config as a silent "ok", even when nongit_ok + * is unset. */ - git_config_early(fn, NULL, repo_config); - if (GIT_REPO_VERSION_READ < repository_format_version) { - if (!nongit_ok) - die ("Expected git repo version <= %d, found %d", - GIT_REPO_VERSION_READ, repository_format_version); - warning("Expected git repo version <= %d, found %d", - GIT_REPO_VERSION_READ, repository_format_version); - warning("Please upgrade Git"); - *nongit_ok = -1; - ret = -1; + if (candidate.version < 0) + return 0; + + if (verify_repository_format(&candidate, &err) < 0) { + if (nongit_ok) { + warning("%s", err.buf); + strbuf_release(&err); + *nongit_ok = -1; + return -1; + } + die("%s", err.buf); } - if (repository_format_version >= 1 && unknown_extensions.nr) { + repository_format_precious_objects = candidate.precious_objects; + string_list_clear(&candidate.unknown_extensions, 0); + if (!has_common) { + if (candidate.is_bare != -1) { + is_bare_repository_cfg = candidate.is_bare; + if (is_bare_repository_cfg == 1) + inside_work_tree = -1; + } + if (candidate.work_tree) { + free(git_work_tree_cfg); + git_work_tree_cfg = candidate.work_tree; + inside_work_tree = -1; + } + } else { + free(candidate.work_tree); + } + + return 0; +} + +int read_repository_format(struct repository_format *format, const char *path) +{ + memset(format, 0, sizeof(*format)); + format->version = -1; + format->is_bare = -1; + string_list_init(&format->unknown_extensions, 1); + git_config_from_file(check_repo_format, path, format); + return format->version; +} + +int verify_repository_format(const struct repository_format *format, + struct strbuf *err) +{ + if (GIT_REPO_VERSION_READ < format->version) { + strbuf_addf(err, _("Expected git repo version <= %d, found %d"), + GIT_REPO_VERSION_READ, format->version); + return -1; + } + + if (format->version >= 1 && format->unknown_extensions.nr) { int i; - if (!nongit_ok) - die("unknown repository extension: %s", - unknown_extensions.items[0].string); + strbuf_addstr(err, _("unknown repository extensions found:")); - for (i = 0; i < unknown_extensions.nr; i++) - warning("unknown repository extension: %s", - unknown_extensions.items[i].string); - *nongit_ok = -1; - ret = -1; + for (i = 0; i < format->unknown_extensions.nr; i++) + strbuf_addf(err, "\n\t%s", + format->unknown_extensions.items[i].string); + return -1; } - strbuf_release(&sb); - return ret; + return 0; } /* @@ -965,30 +997,10 @@ int git_config_perm(const char *var, const char *value) return -(i & 0666); } -int check_repository_format_version(const char *var, const char *value, void *cb) -{ - int ret = check_repo_format(var, value, cb); - if (ret) - return ret; - if (strcmp(var, "core.bare") == 0) { - is_bare_repository_cfg = git_config_bool(var, value); - if (is_bare_repository_cfg == 1) - inside_work_tree = -1; - } else if (strcmp(var, "core.worktree") == 0) { - if (!value) - return config_error_nonbool(var); - free(git_work_tree_cfg); - git_work_tree_cfg = xstrdup(value); - inside_work_tree = -1; - } - return 0; -} - -int check_repository_format(void) +void check_repository_format(void) { check_repository_format_gently(get_git_dir(), NULL); startup_info->have_repository = 1; - return 0; } /* @@ -395,6 +395,12 @@ ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint) return cnt; } +ssize_t strbuf_write(struct strbuf *sb, FILE *f) +{ + return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0; +} + + #define STRBUF_MAXLINK (2*PATH_MAX) int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) @@ -387,6 +387,12 @@ extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); /** + * Write the whole content of the strbuf to the stream not stopping at + * NUL bytes. + */ +extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream); + +/** * Read a line from a FILE *, overwriting the existing contents of * the strbuf. The strbuf_getline*() family of functions share * this signature, but have different line termination conventions. diff --git a/submodule-config.c b/submodule-config.c index 92502b594d..8ac5031ade 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -59,6 +59,7 @@ static void free_one_config(struct submodule_entry *entry) { free((void *) entry->config->path); free((void *) entry->config->name); + free((void *) entry->config->update_strategy.command); free(entry->config); } @@ -194,6 +195,8 @@ static struct submodule *lookup_or_create_by_name(struct submodule_cache *cache, submodule->path = NULL; submodule->url = NULL; + submodule->update_strategy.type = SM_UPDATE_UNSPECIFIED; + submodule->update_strategy.command = NULL; submodule->fetch_recurse = RECURSE_SUBMODULES_NONE; submodule->ignore = NULL; @@ -293,7 +296,7 @@ static int parse_config(const char *var, const char *value, void *data) if (!strcmp(item.buf, "path")) { if (!value) ret = config_error_nonbool(var); - else if (!me->overwrite && submodule->path != NULL) + else if (!me->overwrite && submodule->path) warn_multiple_config(me->commit_sha1, submodule->name, "path"); else { @@ -317,7 +320,7 @@ static int parse_config(const char *var, const char *value, void *data) } else if (!strcmp(item.buf, "ignore")) { if (!value) ret = config_error_nonbool(var); - else if (!me->overwrite && submodule->ignore != NULL) + else if (!me->overwrite && submodule->ignore) warn_multiple_config(me->commit_sha1, submodule->name, "ignore"); else if (strcmp(value, "untracked") && @@ -333,13 +336,23 @@ static int parse_config(const char *var, const char *value, void *data) } else if (!strcmp(item.buf, "url")) { if (!value) { ret = config_error_nonbool(var); - } else if (!me->overwrite && submodule->url != NULL) { + } else if (!me->overwrite && submodule->url) { warn_multiple_config(me->commit_sha1, submodule->name, "url"); } else { free((void *) submodule->url); submodule->url = xstrdup(value); } + } else if (!strcmp(item.buf, "update")) { + if (!value) + ret = config_error_nonbool(var); + else if (!me->overwrite && + submodule->update_strategy.type != SM_UPDATE_UNSPECIFIED) + warn_multiple_config(me->commit_sha1, submodule->name, + "update"); + else if (parse_submodule_update_strategy(value, + &submodule->update_strategy) < 0) + die(_("invalid value for %s"), var); } strbuf_release(&name); @@ -392,8 +405,7 @@ static const struct submodule *config_from(struct submodule_cache *cache, struct hashmap_iter iter; struct submodule_entry *entry; - hashmap_iter_init(&cache->for_name, &iter); - entry = hashmap_iter_next(&iter); + entry = hashmap_iter_first(&cache->for_name, &iter); if (!entry) return NULL; return entry->config; diff --git a/submodule-config.h b/submodule-config.h index 9bfa65af03..e4857f53a8 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -2,6 +2,7 @@ #define SUBMODULE_CONFIG_CACHE_H #include "hashmap.h" +#include "submodule.h" #include "strbuf.h" /* @@ -14,6 +15,7 @@ struct submodule { const char *url; int fetch_recurse; const char *ignore; + struct submodule_update_strategy update_strategy; /* the sha1 blob id of the responsible .gitmodules file */ unsigned char gitmodules_sha1[20]; }; diff --git a/submodule.c b/submodule.c index 62c4356c50..90825e17fa 100644 --- a/submodule.c +++ b/submodule.c @@ -15,6 +15,7 @@ #include "thread-utils.h" static int config_fetch_recurse_submodules = RECURSE_SUBMODULES_ON_DEMAND; +static int parallel_jobs = 1; static struct string_list changed_submodule_paths; static int initialized_fetch_ref_tips; static struct sha1_array ref_tips_before_fetch; @@ -169,7 +170,12 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, int submodule_config(const char *var, const char *value, void *cb) { - if (starts_with(var, "submodule.")) + if (!strcmp(var, "submodule.fetchjobs")) { + parallel_jobs = git_config_int(var, value); + if (parallel_jobs < 0) + die(_("negative values not allowed for submodule.fetchJobs")); + return 0; + } else if (starts_with(var, "submodule.")) return parse_submodule_config_option(var, value); else if (!strcmp(var, "fetch.recursesubmodules")) { config_fetch_recurse_submodules = parse_fetch_recurse_submodules_arg(var, value); @@ -210,6 +216,27 @@ void gitmodules_config(void) } } +int parse_submodule_update_strategy(const char *value, + struct submodule_update_strategy *dst) +{ + free((void*)dst->command); + dst->command = NULL; + if (!strcmp(value, "none")) + dst->type = SM_UPDATE_NONE; + else if (!strcmp(value, "checkout")) + dst->type = SM_UPDATE_CHECKOUT; + else if (!strcmp(value, "rebase")) + dst->type = SM_UPDATE_REBASE; + else if (!strcmp(value, "merge")) + dst->type = SM_UPDATE_MERGE; + else if (skip_prefix(value, "!", &value)) { + dst->type = SM_UPDATE_COMMAND; + dst->command = xstrdup(value); + } else + return -1; + return 0; +} + void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *arg) { @@ -750,6 +777,9 @@ int fetch_populated_submodules(const struct argv_array *options, argv_array_push(&spf.args, "--recurse-submodules-default"); /* default value, "--submodule-prefix" and its value are added later */ + if (max_parallel_jobs < 0) + max_parallel_jobs = parallel_jobs; + calculate_changed_submodule_paths(); run_processes_parallel(max_parallel_jobs, get_next_submodule, @@ -1094,3 +1124,8 @@ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir) strbuf_release(&rel_path); free((void *)real_work_tree); } + +int parallel_submodules(void) +{ + return parallel_jobs; +} diff --git a/submodule.h b/submodule.h index e06eaa5ebb..7ef3775184 100644 --- a/submodule.h +++ b/submodule.h @@ -14,6 +14,21 @@ enum { RECURSE_SUBMODULES_ON = 2 }; +enum submodule_update_type { + SM_UPDATE_UNSPECIFIED = 0, + SM_UPDATE_CHECKOUT, + SM_UPDATE_REBASE, + SM_UPDATE_MERGE, + SM_UPDATE_NONE, + SM_UPDATE_COMMAND +}; + +struct submodule_update_strategy { + enum submodule_update_type type; + const char *command; +}; +#define SUBMODULE_UPDATE_STRATEGY_INIT {SM_UPDATE_UNSPECIFIED, NULL} + int is_staging_gitmodules_ok(void); int update_path_in_gitmodules(const char *oldpath, const char *newpath); int remove_path_from_gitmodules(const char *path); @@ -22,6 +37,8 @@ void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt, const char *path); int submodule_config(const char *var, const char *value, void *cb); void gitmodules_config(void); +int parse_submodule_update_strategy(const char *value, + struct submodule_update_strategy *dst); void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *); void show_submodule_summary(FILE *f, const char *path, const char *line_prefix, @@ -42,5 +59,6 @@ int find_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam struct string_list *needs_pushing); int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_name); void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir); +int parallel_submodules(void); #endif diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index db2ef22e8f..ec2aa8f687 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -1,9 +1,8 @@ #!/bin/sh gpg_version=$(gpg --version 2>&1) -if test $? = 127; then - say "You do not seem to have gpg installed" -else +if test $? != 127 +then # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 # the gpg version 1.0.6 didn't parse trust packets correctly, so for # that version, creation of signed tags using the generated key fails. diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index d7ef44b4a2..03bd31e9f2 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -298,4 +298,15 @@ test_expect_success 'helpers can abort the process' ' test_cmp expect stdout ' +test_expect_success 'empty helper spec resets helper list' ' + test_config credential.helper "verbatim file file" && + check fill "" "verbatim cmdline cmdline" <<-\EOF + -- + username=cmdline + password=cmdline + -- + verbatim: get + EOF +' + test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3d6f1db9da..d934a24417 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -1087,6 +1087,20 @@ test_expect_success 'git -c complains about empty key and value' ' test_must_fail git -c "" rev-parse ' +test_expect_success 'multiple git -c appends config' ' + test_config alias.x "!git -c x.two=2 config --get-regexp ^x\.*" && + cat >expect <<-\EOF && + x.one 1 + x.two 2 + EOF + git -c x.one=1 x >actual && + test_cmp expect actual +' + +test_expect_success 'git -c is not confused by empty environment' ' + GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list +' + test_expect_success 'git config --edit works' ' git config -f tmp test.value no && echo test.value=yes >expect && diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index cbfa41ec61..3acb9926f2 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -213,4 +213,16 @@ test_expect_success 'local clone from linked checkout' ' ( cd here-clone && git fsck ) ' +test_expect_success '"add" worktree with --no-checkout' ' + git worktree add --no-checkout -b swamp swamp && + ! test -e swamp/init.t && + git -C swamp reset --hard && + test_cmp init.t swamp/init.t +' + +test_expect_success '"add" worktree with --checkout' ' + git worktree add --checkout -b swmap2 swamp2 && + test_cmp init.t swamp2/init.t +' + test_done diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh index 46aadc410b..c1379b00c2 100755 --- a/t/t3033-merge-toplevel.sh +++ b/t/t3033-merge-toplevel.sh @@ -19,6 +19,8 @@ test_expect_success setup ' test_commit three && git checkout right && test_commit four && + git checkout --orphan five && + test_commit five && git checkout master ' @@ -133,4 +135,18 @@ test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' ' test_cmp expect actual ' +# two-project merge +test_expect_success 'refuse two-project merge by default' ' + t3033_reset && + git reset --hard four && + test_must_fail git merge five +' + +test_expect_success 'two-project merge with --allow-unrelated-histories' ' + t3033_reset && + git reset --hard four && + git merge --allow-unrelated-histories five && + git diff --exit-code five +' + test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index a897248490..508007fd37 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -403,6 +403,12 @@ test_expect_success 'test deleting branch without config' ' test_i18ncmp expect actual ' +test_expect_success 'deleting currently checked out branch fails' ' + git worktree add -b my7 my7 && + test_must_fail git -C my7 branch -d my7 && + test_must_fail git branch -d my7 +' + test_expect_success 'test --track without .fetch entries' ' git branch --track my8 && test "$(git config branch.my8.remote)" && diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 544f9ad508..b79f442acf 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -771,7 +771,6 @@ test_expect_success 'rebase-i history with funny messages' ' test_cmp expect actual ' - test_expect_success 'prepare for rebase -i --exec' ' git checkout master && git checkout -b execute && @@ -780,7 +779,6 @@ test_expect_success 'prepare for rebase -i --exec' ' test_commit three_exec main.txt three_exec ' - test_expect_success 'running "git rebase -i --exec git show HEAD"' ' set_fake_editor && git rebase -i --exec "git show HEAD" HEAD~2 >actual && @@ -793,7 +791,6 @@ test_expect_success 'running "git rebase -i --exec git show HEAD"' ' test_cmp expected actual ' - test_expect_success 'running "git rebase --exec git show HEAD -i"' ' git reset --hard execute && set_fake_editor && @@ -807,7 +804,6 @@ test_expect_success 'running "git rebase --exec git show HEAD -i"' ' test_cmp expected actual ' - test_expect_success 'running "git rebase -ix git show HEAD"' ' git reset --hard execute && set_fake_editor && @@ -835,7 +831,6 @@ test_expect_success 'rebase -ix with several <CMD>' ' test_cmp expected actual ' - test_expect_success 'rebase -ix with several instances of --exec' ' git reset --hard execute && set_fake_editor && @@ -850,7 +845,6 @@ test_expect_success 'rebase -ix with several instances of --exec' ' test_cmp expected actual ' - test_expect_success 'rebase -ix with --autosquash' ' git reset --hard execute && git checkout -b autosquash && @@ -876,16 +870,15 @@ test_expect_success 'rebase -ix with --autosquash' ' test_cmp expected actual ' - -test_expect_success 'rebase --exec without -i shows error message' ' +test_expect_success 'rebase --exec works without -i ' ' git reset --hard execute && - set_fake_editor && - test_must_fail git rebase --exec "git show HEAD" HEAD~2 2>actual && - echo "The --exec option must be used with the --interactive option" >expected && - test_i18ncmp expected actual + rm -rf exec_output && + EDITOR="echo >invoked_editor" git rebase --exec "echo a line >>exec_output" HEAD~2 2>actual && + test_i18ngrep "Successfully rebased and updated" actual && + test_line_count = 2 exec_output && + test_path_is_missing invoked_editor ' - test_expect_success 'rebase -i --exec without <CMD>' ' git reset --hard execute && set_fake_editor && diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 0b52105728..73a39f2923 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -133,7 +133,7 @@ test_expect_success 'set up second root and merge' ' rm A B C && test_commit 6 D && git checkout other && - git merge third + git merge --allow-unrelated-histories third ' cat > expect-third <<'EOF' diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 2f327b7495..0d1fa45d25 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -9,21 +9,84 @@ test_description='Test rename detection in diff engine. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh -echo >path0 'Line 1 -Line 2 -Line 3 -Line 4 -Line 5 -Line 6 -Line 7 -Line 8 -Line 9 -Line 10 -line 11 -Line 12 -Line 13 -Line 14 -Line 15 +test_expect_success 'setup' ' + cat >path0 <<-\EOF && + Line 1 + Line 2 + Line 3 + Line 4 + Line 5 + Line 6 + Line 7 + Line 8 + Line 9 + Line 10 + line 11 + Line 12 + Line 13 + Line 14 + Line 15 + EOF + cat >expected <<-\EOF && + diff --git a/path0 b/path1 + rename from path0 + rename to path1 + --- a/path0 + +++ b/path1 + @@ -8,7 +8,7 @@ Line 7 + Line 8 + Line 9 + Line 10 + -line 11 + +Line 11 + Line 12 + Line 13 + Line 14 + EOF + cat >no-rename <<-\EOF + diff --git a/path0 b/path0 + deleted file mode 100644 + index fdbec44..0000000 + --- a/path0 + +++ /dev/null + @@ -1,15 +0,0 @@ + -Line 1 + -Line 2 + -Line 3 + -Line 4 + -Line 5 + -Line 6 + -Line 7 + -Line 8 + -Line 9 + -Line 10 + -line 11 + -Line 12 + -Line 13 + -Line 14 + -Line 15 + diff --git a/path1 b/path1 + new file mode 100644 + index 0000000..752c50e + --- /dev/null + +++ b/path1 + @@ -0,0 +1,15 @@ + +Line 1 + +Line 2 + +Line 3 + +Line 4 + +Line 5 + +Line 6 + +Line 7 + +Line 8 + +Line 9 + +Line 10 + +Line 11 + +Line 12 + +Line 13 + +Line 14 + +Line 15 + EOF ' test_expect_success \ @@ -43,27 +106,27 @@ test_expect_success \ test_expect_success \ 'git diff-index -p -M after rename and editing.' \ 'git diff-index -p -M $tree >current' -cat >expected <<\EOF -diff --git a/path0 b/path1 -rename from path0 -rename to path1 ---- a/path0 -+++ b/path1 -@@ -8,7 +8,7 @@ Line 7 - Line 8 - Line 9 - Line 10 --line 11 -+Line 11 - Line 12 - Line 13 - Line 14 -EOF + test_expect_success \ 'validate the output.' \ 'compare_diff_patch current expected' +test_expect_success 'test diff.renames=true' ' + git -c diff.renames=true diff --cached $tree >current && + compare_diff_patch current expected +' + +test_expect_success 'test diff.renames=false' ' + git -c diff.renames=false diff --cached $tree >current && + compare_diff_patch current no-rename +' + +test_expect_success 'test diff.renames unset' ' + git diff --cached $tree >current && + compare_diff_patch current expected +' + test_expect_success 'favour same basenames over different ones' ' cp path1 another-path && git add another-path && @@ -77,6 +140,17 @@ test_expect_success 'favour same basenames even with minor differences' ' git show HEAD:path1 | sed "s/15/16/" > subdir/path1 && git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"' +test_expect_success 'two files with same basename and same content' ' + git reset --hard && + mkdir -p dir/A dir/B && + cp path1 dir/A/file && + cp path1 dir/B/file && + git add dir && + git commit -m 2 && + git mv dir other-dir && + git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file" +' + test_expect_success 'setup for many rename source candidates' ' git reset --hard && for i in 0 1 2 3 4 5 6 7 8 9; diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 6ec6072118..94ef5000e7 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -90,6 +90,8 @@ test_expect_success setup ' git commit -m "Rearranged lines in dir/sub" && git checkout master && + git config diff.renames false && + git show-branch ' diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 3b99434e3e..eed2981b96 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -549,7 +549,7 @@ test_expect_success 'cover-letter inherits diff options' ' git mv file foo && git commit -m foo && - git format-patch --cover-letter -1 && + git format-patch --no-renames --cover-letter -1 && check_patch 0000-cover-letter.patch && ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch && git format-patch --cover-letter -1 -M && @@ -703,7 +703,7 @@ test_expect_success 'options no longer allowed for format-patch' ' test_expect_success 'format-patch --numstat should produce a patch' ' git format-patch --numstat --stdout master..side > output && - test 6 = $(grep "^diff --git a/" output | wc -l)' + test 5 = $(grep "^diff --git a/" output | wc -l)' test_expect_success 'format-patch -- <path>' ' git format-patch master..side -- file 2>error && diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh index 3b8b7921d6..447a8ffa3a 100755 --- a/t/t4047-diff-dirstat.sh +++ b/t/t4047-diff-dirstat.sh @@ -248,7 +248,8 @@ EOF git rm -r src/move/unchanged && git rm -r src/move/changed && git rm -r src/move/rearranged && - git commit -m "changes" + git commit -m "changes" && + git config diff.renames false ' cat <<EOF >expect_diff_stat diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index f5e63670fa..a9773658f0 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -115,7 +115,7 @@ EOF ' test_expect_success !MINGW 'shortlog from non-git directory' ' - git log HEAD >log && + git log --no-expand-tabs HEAD >log && GIT_DIR=non-existing git shortlog -w <log >out && test_cmp expect out ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index cb82eb7e66..128ba93537 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -101,8 +101,8 @@ test_expect_success 'oneline' ' test_expect_success 'diff-filter=A' ' - git log --pretty="format:%s" --diff-filter=A HEAD > actual && - git log --pretty="format:%s" --diff-filter A HEAD > actual-separate && + git log --no-renames --pretty="format:%s" --diff-filter=A HEAD > actual && + git log --no-renames --pretty="format:%s" --diff-filter A HEAD > actual-separate && printf "fifth\nfourth\nthird\ninitial" > expect && test_cmp expect actual && test_cmp expect actual-separate @@ -119,7 +119,7 @@ test_expect_success 'diff-filter=M' ' test_expect_success 'diff-filter=D' ' - actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) && + actual=$(git log --no-renames --pretty="format:%s" --diff-filter=D HEAD) && expect=$(echo sixth ; echo third) && verbose test "$actual" = "$expect" @@ -848,7 +848,7 @@ sanitize_output () { } test_expect_success 'log --graph with diff and stats' ' - git log --graph --pretty=short --stat -p >actual && + git log --no-renames --graph --pretty=short --stat -p >actual && sanitize_output >actual.sanitized <actual && test_i18ncmp expect actual.sanitized ' diff --git a/t/t4213-log-tabexpand.sh b/t/t4213-log-tabexpand.sh new file mode 100755 index 0000000000..e01a8f6ac9 --- /dev/null +++ b/t/t4213-log-tabexpand.sh @@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='log/show --expand-tabs' + +. ./test-lib.sh + +HT=" " +title='tab indent at the beginning of the title line' +body='tab indent on a line in the body' + +# usage: count_expand $indent $numSP $numHT @format_args +count_expand () +{ + expect= + count=$(( $1 + $2 )) ;# expected spaces + while test $count -gt 0 + do + expect="$expect " + count=$(( $count - 1 )) + done + shift 2 + count=$1 ;# expected tabs + while test $count -gt 0 + do + expect="$expect$HT" + count=$(( $count - 1 )) + done + shift + + # The remainder of the command line is "git show -s" options + case " $* " in + *' --pretty=short '*) + line=$title ;; + *) + line=$body ;; + esac + + # Prefix the output with the command line arguments, and + # replace SP with a dot both in the expecte and actual output + # so that test_cmp would show the differene together with the + # breakage in a way easier to consume by the debugging user. + { + echo "git show -s $*" + echo "$expect$line" + } | sed -e 's/ /./g' >expect + + { + echo "git show -s $*" + git show -s "$@" | + sed -n -e "/$line\$/p" + } | sed -e 's/ /./g' >actual + + test_cmp expect actual +} + +test_expand () +{ + fmt=$1 + case "$fmt" in + *=raw | *=short | *=email) + default="0 1" ;; + *) + default="8 0" ;; + esac + case "$fmt" in + *=email) + in=0 ;; + *) + in=4 ;; + esac + test_expect_success "expand/no-expand${fmt:+ for $fmt}" ' + count_expand $in $default $fmt && + count_expand $in 8 0 $fmt --expand-tabs && + count_expand $in 8 0 --expand-tabs $fmt && + count_expand $in 8 0 $fmt --expand-tabs=8 && + count_expand $in 8 0 --expand-tabs=8 $fmt && + count_expand $in 0 1 $fmt --no-expand-tabs && + count_expand $in 0 1 --no-expand-tabs $fmt && + count_expand $in 0 1 $fmt --expand-tabs=0 && + count_expand $in 0 1 --expand-tabs=0 $fmt && + count_expand $in 4 0 $fmt --expand-tabs=4 && + count_expand $in 4 0 --expand-tabs=4 $fmt + ' +} + +test_expect_success 'setup' ' + test_tick && + sed -e "s/Q/$HT/g" <<-EOF >msg && + Q$title + + Q$body + EOF + git commit --allow-empty -F msg +' + +test_expand "" +test_expand --pretty +test_expand --pretty=short +test_expand --pretty=medium +test_expand --pretty=full +test_expand --pretty=fuller +test_expand --pretty=raw +test_expand --pretty=email + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 04cea97f87..305ca7a930 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -128,6 +128,18 @@ test_expect_success 'denyNonFastforwards trumps --force' ' test "$victim_orig" = "$victim_head" ' +test_expect_success 'send-pack --all sends all branches' ' + # make sure we have at least 2 branches with different + # values, just to be thorough + git branch other-branch HEAD^ && + + git init --bare all.git && + git send-pack --all all.git && + git for-each-ref refs/heads >expect && + git -C all.git for-each-ref refs/heads >actual && + test_cmp expect actual +' + test_expect_success 'push --all excludes remote-tracking hierarchy' ' mkdir parent && ( diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 9b9bec468a..91a69fc33a 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -259,7 +259,8 @@ test_expect_success 'clone shallow object count' ' test_expect_success 'pull in shallow repo with missing merge base' ' ( cd shallow && - test_must_fail git pull --depth 4 .. A + git fetch --depth 4 .. A + test_must_fail git merge --allow-unrelated-histories FETCH_HEAD ) ' @@ -279,9 +280,10 @@ test_expect_success 'clone shallow depth count' ' test_expect_success 'clone shallow object count' ' ( cd shallow && + git prune && git count-objects -v ) > count.shallow && - grep "^count: 55" count.shallow + grep "^count: 54" count.shallow ' test_expect_success 'fetch --no-shallow on full repo' ' diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index c952d5ef5c..739c089d50 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -9,6 +9,24 @@ modify () { mv "$2.x" "$2" } +test_pull_autostash () { + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + git pull "$@" . copy && + test_cmp_rev HEAD^ copy && + test "$(cat new_file)" = dirty && + test "$(cat file)" = "modified again" +} + +test_pull_autostash_fail () { + git reset --hard before-rebase && + echo dirty >new_file && + git add new_file && + test_must_fail git pull "$@" . copy 2>err && + test_i18ngrep "uncommitted changes." err +} + test_expect_success setup ' echo file >file && git add file && @@ -247,15 +265,47 @@ test_expect_success '--rebase fails with multiple branches' ' test_expect_success 'pull --rebase succeeds with dirty working directory and rebase.autostash set' ' test_config rebase.autostash true && - git reset --hard before-rebase && - echo dirty >new_file && - git add new_file && - git pull --rebase . copy && - test_cmp_rev HEAD^ copy && - test "$(cat new_file)" = dirty && - test "$(cat file)" = "modified again" + test_pull_autostash --rebase ' +test_expect_success 'pull --rebase --autostash & rebase.autostash=true' ' + test_config rebase.autostash true && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash=false' ' + test_config rebase.autostash false && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --autostash & rebase.autostash unset' ' + test_unconfig rebase.autostash && + test_pull_autostash --rebase --autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash=true' ' + test_config rebase.autostash true && + test_pull_autostash_fail --rebase --no-autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash=false' ' + test_config rebase.autostash false && + test_pull_autostash_fail --rebase --no-autostash +' + +test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' ' + test_unconfig rebase.autostash && + test_pull_autostash_fail --rebase --no-autostash +' + +for i in --autostash --no-autostash +do + test_expect_success "pull $i (without --rebase) is illegal" ' + test_must_fail git pull $i . copy 2>err && + test_i18ngrep "only valid with --rebase" err + ' +done + test_expect_success 'pull.rebase' ' git reset --hard before-rebase && test_config pull.rebase true && @@ -264,6 +314,16 @@ test_expect_success 'pull.rebase' ' test new = "$(git show HEAD:file2)" ' +test_expect_success 'pull --autostash & pull.rebase=true' ' + test_config pull.rebase true && + test_pull_autostash --autostash +' + +test_expect_success 'pull --no-autostash & pull.rebase=true' ' + test_config pull.rebase true && + test_pull_autostash_fail --no-autostash +' + test_expect_success 'branch.to-rebase.rebase' ' git reset --hard before-rebase && test_config branch.to-rebase.rebase true && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 1241146227..954d0e43f5 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -471,4 +471,18 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea test_i18ncmp expect.err actual.err ' +test_expect_success 'fetching submodules respects parallel settings' ' + git config fetch.recurseSubmodules true && + ( + cd downstream && + GIT_TRACE=$(pwd)/trace.out git fetch --jobs 7 && + grep "7 tasks" trace.out && + git config submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git fetch && + grep "8 tasks" trace.out && + GIT_TRACE=$(pwd)/trace.out git fetch --jobs 9 && + grep "9 tasks" trace.out + ) +' + test_done diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 64146352ae..48e2ab62da 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -91,6 +91,23 @@ test_expect_success 'configured username does not override URL' ' expect_askpass pass user@host ' +test_expect_success 'cmdline credential config passes into submodules' ' + git init super && + set_askpass user@host pass@host && + ( + cd super && + git submodule add "$HTTPD_URL/auth/dumb/repo.git" sub && + git commit -m "add submodule" + ) && + set_askpass wrong pass@host && + test_must_fail git clone --recursive super super-clone && + rm -rf super-clone && + set_askpass wrong pass@host && + git -c "credential.$HTTP_URL.username=user@host" \ + clone --recursive super super-clone && + expect_askpass pass user@host +' + test_expect_success 'fetch changes via http' ' echo content >>file && git commit -a -m two && diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 66cda17ef3..20e3e2554a 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -47,7 +47,9 @@ test_expect_success 'setup roots, merges and octopuses' ' git checkout -b yetanotherbranch four && test_commit eight && git checkout master && - test_merge normalmerge newroot && + test_tick && + git merge --allow-unrelated-histories -m normalmerge newroot && + git tag normalmerge && test_tick && git merge -m tripus sidebranch anotherbranch && git tag tripus && diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 39b3238da2..e0c5f44cac 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -215,11 +215,13 @@ test_expect_success 'criss-cross merge-base for octopus-step' ' git reset --hard E && test_commit CC2 && test_tick && - git merge -s ours CC1 && + # E is a root commit unrelated to MMR root on which CC1 is based + git merge -s ours --allow-unrelated-histories CC1 && test_commit CC-o && test_commit CCB && git reset --hard CC1 && - git merge -s ours CC2 && + # E is a root commit unrelated to MMR root on which CC1 is based + git merge -s ours --allow-unrelated-histories CC2 && test_commit CCA && git rev-parse CC1 CC2 >expected && diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index b89cd6b07a..2a0fbb87b1 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -71,7 +71,7 @@ test_expect_success setup ' note J && git checkout master && - test_tick && git merge -m "Coolest" unrelated && + test_tick && git merge --allow-unrelated-histories -m "Coolest" unrelated && note K && echo "Immaterial" >elif && diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh index 04c0509c47..ef0cbceafe 100755 --- a/t/t6026-merge-attr.sh +++ b/t/t6026-merge-attr.sh @@ -176,7 +176,8 @@ test_expect_success 'up-to-date merge without common ancestor' ' test_tick && ( cd repo1 && - git pull ../repo2 master + git fetch ../repo2 master && + git merge --allow-unrelated-histories FETCH_HEAD ) ' diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh index 73fc240e85..3e692454a7 100755 --- a/t/t6029-merge-subtree.sh +++ b/t/t6029-merge-subtree.sh @@ -49,7 +49,7 @@ test_expect_success 'setup' ' test_expect_success 'initial merge' ' git remote add -f gui ../git-gui && - git merge -s ours --no-commit gui/master && + git merge -s ours --no-commit --allow-unrelated-histories gui/master && git read-tree --prefix=git-gui/ -u gui/master && git commit -m "Merge git-gui as our subdirectory" && git checkout -b work && diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 10b1452766..1c6952d049 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -19,7 +19,7 @@ test_expect_success 'setup' ' git checkout --orphan tmp && test_commit start2 && git checkout master && - git merge -m next start2 && + git merge -m next --allow-unrelated-histories start2 && test_commit final && test_seq 40 | diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index bcf472bf51..70afb44271 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -5,11 +5,14 @@ test_description='test for-each-refs usage of ref-filter APIs' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh -if ! test_have_prereq GPG -then - skip_all="skipping for-each-ref tests, GPG not available" - test_done -fi +test_prepare_expect () { + if test_have_prereq GPG + then + cat + else + sed '/signed/d' + fi +} test_expect_success 'setup some history and refs' ' test_commit one && @@ -17,8 +20,13 @@ test_expect_success 'setup some history and refs' ' test_commit three && git checkout -b side && test_commit four && - git tag -s -m "A signed tag message" signed-tag && - git tag -s -m "Annonated doubly" double-tag signed-tag && + git tag -m "An annotated tag" annotated-tag && + git tag -m "Annonated doubly" doubly-annotated-tag annotated-tag && + if test_have_prereq GPG + then + git tag -s -m "A signed tag" signed-tag && + git tag -s -m "Signed doubly" doubly-signed-tag signed-tag + fi && git checkout master && git update-ref refs/odd/spot master ' @@ -34,8 +42,9 @@ test_expect_success 'filtering with --points-at' ' ' test_expect_success 'check signed tags with --points-at' ' - sed -e "s/Z$//" >expect <<-\EOF && + test_prepare_expect <<-\EOF | sed -e "s/Z$//" >expect && refs/heads/side Z + refs/tags/annotated-tag four refs/tags/four Z refs/tags/signed-tag four EOF @@ -56,9 +65,11 @@ test_expect_success 'filtering with --merged' ' ' test_expect_success 'filtering with --no-merged' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && refs/heads/side - refs/tags/double-tag + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag refs/tags/four refs/tags/signed-tag EOF @@ -67,11 +78,13 @@ test_expect_success 'filtering with --no-merged' ' ' test_expect_success 'filtering with --contains' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && refs/heads/master refs/heads/side refs/odd/spot - refs/tags/double-tag + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag refs/tags/four refs/tags/signed-tag refs/tags/three @@ -86,11 +99,13 @@ test_expect_success '%(color) must fail' ' ' test_expect_success 'left alignment is default' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && refname is refs/heads/master |refs/heads/master refname is refs/heads/side |refs/heads/side refname is refs/odd/spot |refs/odd/spot - refname is refs/tags/double-tag|refs/tags/double-tag + refname is refs/tags/annotated-tag|refs/tags/annotated-tag + refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag refname is refs/tags/four |refs/tags/four refname is refs/tags/one |refs/tags/one refname is refs/tags/signed-tag|refs/tags/signed-tag @@ -102,11 +117,13 @@ test_expect_success 'left alignment is default' ' ' test_expect_success 'middle alignment' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && | refname is refs/heads/master |refs/heads/master | refname is refs/heads/side |refs/heads/side | refname is refs/odd/spot |refs/odd/spot - |refname is refs/tags/double-tag|refs/tags/double-tag + |refname is refs/tags/annotated-tag|refs/tags/annotated-tag + |refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + |refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag | refname is refs/tags/four |refs/tags/four | refname is refs/tags/one |refs/tags/one |refname is refs/tags/signed-tag|refs/tags/signed-tag @@ -118,11 +135,13 @@ test_expect_success 'middle alignment' ' ' test_expect_success 'right alignment' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && | refname is refs/heads/master|refs/heads/master | refname is refs/heads/side|refs/heads/side | refname is refs/odd/spot|refs/odd/spot - |refname is refs/tags/double-tag|refs/tags/double-tag + |refname is refs/tags/annotated-tag|refs/tags/annotated-tag + |refname is refs/tags/doubly-annotated-tag|refs/tags/doubly-annotated-tag + |refname is refs/tags/doubly-signed-tag|refs/tags/doubly-signed-tag | refname is refs/tags/four|refs/tags/four | refname is refs/tags/one|refs/tags/one |refname is refs/tags/signed-tag|refs/tags/signed-tag @@ -133,11 +152,13 @@ test_expect_success 'right alignment' ' test_cmp expect actual ' -cat >expect <<-\EOF +test_prepare_expect >expect <<-\EOF | refname is refs/heads/master |refs/heads/master | refname is refs/heads/side |refs/heads/side | refname is refs/odd/spot |refs/odd/spot -| refname is refs/tags/double-tag |refs/tags/double-tag +| refname is refs/tags/annotated-tag |refs/tags/annotated-tag +|refname is refs/tags/doubly-annotated-tag |refs/tags/doubly-annotated-tag +| refname is refs/tags/doubly-signed-tag |refs/tags/doubly-signed-tag | refname is refs/tags/four |refs/tags/four | refname is refs/tags/one |refs/tags/one | refname is refs/tags/signed-tag |refs/tags/signed-tag @@ -178,11 +199,13 @@ EOF # Individual atoms inside %(align:...) and %(end) must not be quoted. test_expect_success 'alignment with format quote' " - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && |' '\''master| A U Thor'\'' '| |' '\''side| A U Thor'\'' '| |' '\''odd/spot| A U Thor'\'' '| - |' '\''double-tag| '\'' '| + |' '\''annotated-tag| '\'' '| + |' '\''doubly-annotated-tag| '\'' '| + |' '\''doubly-signed-tag| '\'' '| |' '\''four| A U Thor'\'' '| |' '\''one| A U Thor'\'' '| |' '\''signed-tag| '\'' '| @@ -194,11 +217,13 @@ test_expect_success 'alignment with format quote' " " test_expect_success 'nested alignment with quote formatting' " - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && |' master '| |' side '| |' odd/spot '| - |' double-tag '| + |' annotated-tag '| + |'doubly-annotated-tag '| + |'doubly-signed-tag '| |' four '| |' one '| |' signed-tag '| @@ -210,14 +235,16 @@ test_expect_success 'nested alignment with quote formatting' " " test_expect_success 'check `%(contents:lines=1)`' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && master |three side |four odd/spot |three - double-tag |Annonated doubly + annotated-tag |An annotated tag + doubly-annotated-tag |Annonated doubly + doubly-signed-tag |Signed doubly four |four one |one - signed-tag |A signed tag message + signed-tag |A signed tag three |three two |two EOF @@ -226,11 +253,13 @@ test_expect_success 'check `%(contents:lines=1)`' ' ' test_expect_success 'check `%(contents:lines=0)`' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && master | side | odd/spot | - double-tag | + annotated-tag | + doubly-annotated-tag | + doubly-signed-tag | four | one | signed-tag | @@ -242,14 +271,16 @@ test_expect_success 'check `%(contents:lines=0)`' ' ' test_expect_success 'check `%(contents:lines=99999)`' ' - cat >expect <<-\EOF && + test_prepare_expect >expect <<-\EOF && master |three side |four odd/spot |three - double-tag |Annonated doubly + annotated-tag |An annotated tag + doubly-annotated-tag |Annonated doubly + doubly-signed-tag |Signed doubly four |four one |one - signed-tag |A signed tag message + signed-tag |A signed tag three |three two |two EOF diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index cf3469b142..f9b7d79af5 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -775,6 +775,47 @@ test_expect_success GPG '-s implies annotated tag' ' test_cmp expect actual ' +get_tag_header forcesignannotated-implied-sign $commit commit $time >expect +echo "A message" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success GPG \ + 'git tag -s implied if configured with tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag -m "A message" forcesignannotated-implied-sign && + get_tag_msg forcesignannotated-implied-sign >actual && + test_cmp expect actual +' + +test_expect_success GPG \ + 'lightweight with no message when configured with tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag forcesignannotated-lightweight && + tag_exists forcesignannotated-lightweight && + test_must_fail git tag -v forcesignannotated-no-message +' + +get_tag_header forcesignannotated-annotate $commit commit $time >expect +echo "A message" >>expect +test_expect_success GPG \ + 'git tag -a disable configured tag.forcesignannotated' \ + 'test_config tag.forcesignannotated true && + git tag -a -m "A message" forcesignannotated-annotate && + get_tag_msg forcesignannotated-annotate >actual && + test_cmp expect actual && + test_must_fail git tag -v forcesignannotated-annotate +' + +get_tag_header forcesignannotated-disabled $commit commit $time >expect +echo "A message" >>expect +echo '-----BEGIN PGP SIGNATURE-----' >>expect +test_expect_success GPG \ + 'git tag --sign enable GPG sign' \ + 'test_config tag.forcesignannotated false && + git tag --sign -m "A message" forcesignannotated-disabled && + get_tag_msg forcesignannotated-disabled >actual && + test_cmp expect actual +' + test_expect_success GPG \ 'trying to create a signed tag with non-existing -F file should fail' ' ! test -f nonexistingfile && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index e1abd19230..17d7a98207 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -462,7 +462,7 @@ test_expect_success 'update --init' ' git config --remove-section submodule.example && test_must_fail git config submodule.example.url && - git submodule update init > update.out && + git submodule update init 2> update.out && cat update.out && test_i18ngrep "not initialized" update.out && test_must_fail git rev-parse --resolve-git-dir init/.git && @@ -480,7 +480,7 @@ test_expect_success 'update --init from subdirectory' ' mkdir -p sub && ( cd sub && - git submodule update ../init >update.out && + git submodule update ../init 2>update.out && cat update.out && test_i18ngrep "not initialized" update.out && test_must_fail git rev-parse --resolve-git-dir ../init/.git && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 68ea31d693..0791df75ac 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -774,4 +774,31 @@ test_expect_success 'submodule update --recursive drops module name before recur test_i18ngrep "Submodule path .deeper/submodule/subsubmodule.: checked out" actual ) ' + +test_expect_success 'submodule update can be run in parallel' ' + (cd super2 && + GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 7 && + grep "7 tasks" trace.out && + git config submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git submodule update && + grep "8 tasks" trace.out && + GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 9 && + grep "9 tasks" trace.out + ) +' + +test_expect_success 'git clone passes the parallel jobs config on to submodules' ' + test_when_finished "rm -rf super4" && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 7 . super4 && + grep "7 tasks" trace.out && + rm -rf super4 && + git config --global submodule.fetchJobs 8 && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules . super4 && + grep "8 tasks" trace.out && + rm -rf super4 && + GIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 9 . super4 && + grep "9 tasks" trace.out && + rm -rf super4 +' + test_done diff --git a/t/t7412-submodule--helper.sh b/t/t7412-submodule--helper.sh new file mode 100755 index 0000000000..149d42864f --- /dev/null +++ b/t/t7412-submodule--helper.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright (c) 2016 Jacob Keller +# + +test_description='Basic plumbing support of submodule--helper + +This test verifies the submodule--helper plumbing command used to implement +git-submodule. +' + +. ./test-lib.sh + +test_expect_success 'sanitize-config clears configuration' ' + git -c user.name="Some User" submodule--helper sanitize-config >actual && + test_must_be_empty actual +' + +sq="'" +test_expect_success 'sanitize-config keeps credential.helper' ' + git -c credential.helper=helper submodule--helper sanitize-config >actual && + echo "${sq}credential.helper=helper${sq}" >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index 6568429753..a9b266f0d3 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -212,4 +212,18 @@ test_expect_success 'blame file with CRLF attributes text' ' grep "A U Thor" actual ' +test_expect_success 'blame file with CRLF core.autocrlf=true' ' + git config core.autocrlf false && + printf "testcase\r\n" >crlfinrepo && + >.gitattributes && + git add crlfinrepo && + git commit -m "add crlfinrepo" && + git config core.autocrlf true && + mv crlfinrepo tmp && + git checkout crlfinrepo && + rm tmp && + git blame crlfinrepo >actual && + grep "A U Thor" actual +' + test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index d708cbf032..432c61d246 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -45,7 +45,8 @@ test_expect_success 'setup' ' touch secondrootfile && git add secondrootfile && git commit -m "second root") && - git pull secondroot master && + git fetch secondroot master && + git merge --allow-unrelated-histories FETCH_HEAD && git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" && diff --git a/t/t9828-git-p4-map-user.sh b/t/t9828-git-p4-map-user.sh new file mode 100755 index 0000000000..e20395c89f --- /dev/null +++ b/t/t9828-git-p4-map-user.sh @@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='Clone repositories and map users' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'Create a repo with different users' ' + client_view "//depot/... //client/..." && + ( + cd "$cli" && + + >author.txt && + p4 add author.txt && + p4 submit -d "Add file author\\n" && + + P4USER=mmax && + >max.txt && + p4 add max.txt && + p4 submit -d "Add file max" && + + P4USER=eri && + >moritz.txt && + p4 add moritz.txt && + p4 submit -d "Add file moritz" && + + P4USER=no && + >nobody.txt && + p4 add nobody.txt && + p4 submit -d "Add file nobody" + ) +' + +test_expect_success 'Clone repo root path with all history' ' + client_view "//depot/... //client/..." && + test_when_finished cleanup_git && + ( + cd "$git" && + git init . && + git config --add git-p4.mapUser "mmax = Max Musterman <max@example.com> " && + git config --add git-p4.mapUser " eri=Erika Musterman <erika@example.com>" && + git p4 clone --use-client-spec --destination="$git" //depot@all && + cat >expect <<-\EOF && + no <no@client> + Erika Musterman <erika@example.com> + Max Musterman <max@example.com> + Dr. author <author@example.com> + EOF + git log --format="%an <%ae>" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/wt-status.c b/wt-status.c index ef7486474a..1ea2ebe4c0 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1063,9 +1063,7 @@ static void abbrev_sha1_in_line(struct strbuf *line) strbuf_addf(line, "%s", split[i]->buf); } } - for (i = 0; split[i]; i++) - strbuf_release(split[i]); - + strbuf_list_free(split); } static void read_rebase_todolist(const char *fname, struct string_list *lines) |