From: Junio C Hamano Date: Tue, 10 Apr 2012 19:44:45 +0000 (-0700) Subject: Merge branch 'maint-1.7.8' into maint-1.7.9 X-Git-Url: https://git.tokkee.org/?p=git.git;a=commitdiff_plain;h=6eab5f2f14806cf6a7b665d96766a59c13a41f4b;hp=b1bcfbe34439ff6d1a04d0bddb01eebe4df418a5 Merge branch 'maint-1.7.8' into maint-1.7.9 * maint-1.7.8: Documentation/gitweb: trivial English fixes fetch/receive: remove over-pessimistic connectivity check --- diff --git a/.gitignore b/.gitignore index 122336c50..3b7680ea1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,9 @@ /git-commit-tree /git-config /git-count-objects +/git-credential-cache +/git-credential-cache--daemon +/git-credential-store /git-cvsexportcommit /git-cvsimport /git-cvsserver @@ -167,6 +170,7 @@ /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /test-chmtime +/test-credential /test-ctype /test-date /test-delta diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index fe1c1e5bc..45577117c 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -35,10 +35,22 @@ For shell scripts specifically (not exhaustive): - Case arms are indented at the same depth as case and esac lines. + - Redirection operators should be written with space before, but no + space after them. In other words, write 'echo test >"$file"' + instead of 'echo test> $file' or 'echo test > $file'. Note that + even though it is not required by POSIX to double-quote the + redirection target in a variable (as shown above), our code does so + because some versions of bash issue a warning without the quotes. + - We prefer $( ... ) for command substitution; unlike ``, it properly nests. It should have been the way Bourne spelled it from day one, but unfortunately isn't. + - If you want to find out if a command is available on the user's + $PATH, you should use 'type ', instead of 'which '. + The output of 'which' is not machine parseable and its exit code + is not reliable across platforms. + - We use POSIX compliant parameter substitutions and avoid bashisms; namely: @@ -81,6 +93,10 @@ For shell scripts specifically (not exhaustive): are ERE elements not BRE (note that \? and \+ are not even part of BRE -- making them accessible from BRE is a GNU extension). + - Use Git's gettext wrappers in git-sh-i18n to make the user + interface translatable. See "Marking strings for translation" in + po/README. + For C programs: - We use tabs to indent, and interpret tabs as taking up to @@ -144,6 +160,9 @@ For C programs: - When we pass pair to functions, we should try to pass them in that order. + - Use Git's gettext wrappers to make the user interface + translatable. See "Marking strings for translation" in po/README. + Writing Documentation: Every user-visible change should be reflected in the documentation. diff --git a/Documentation/Makefile b/Documentation/Makefile index 304b31ede..d40e211f2 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -7,6 +7,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \ MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \ gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \ gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt +MAN7_TXT += gitcredentials.txt MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT)) @@ -19,7 +20,10 @@ ARTICLES += everyday ARTICLES += git-tools ARTICLES += git-bisect-lk2009 # with their own formatting rules. -SP_ARTICLES = howto/revert-branch-rebase howto/using-merge-subtree user-manual +SP_ARTICLES = user-manual +SP_ARTICLES += howto/revert-branch-rebase +SP_ARTICLES += howto/using-merge-subtree +SP_ARTICLES += howto/using-signed-tag-in-pull-request API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) SP_ARTICLES += $(API_DOCS) SP_ARTICLES += technical/api-index diff --git a/Documentation/RelNotes/1.7.9.1.txt b/Documentation/RelNotes/1.7.9.1.txt new file mode 100644 index 000000000..6957183db --- /dev/null +++ b/Documentation/RelNotes/1.7.9.1.txt @@ -0,0 +1,63 @@ +Git v1.7.9.1 Release Notes +========================== + +Fixes since v1.7.9 +------------------ + + * The makefile allowed environment variable X seep into it result in + command names suffixed with unnecessary strings. + + * The set of included header files in compat/inet-{ntop,pton} + wrappers was updated for Windows some time ago, but in a way that + broke Solaris build. + + * rpmbuild noticed an unpackaged but installed *.mo file and failed. + + * Subprocesses spawned from various git programs were often left running + to completion even when the top-level process was killed. + + * "git add -e" learned not to show a diff for an otherwise unmodified + submodule that only has uncommitted local changes in the patch + prepared by for the user to edit. + + * Typo in "git branch --edit-description my-tpoic" was not diagnosed. + + * Using "git grep -l/-L" together with options -W or --break may not + make much sense as the output is to only count the number of hits + and there is no place for file breaks, but the latter options made + "-l/-L" to miscount the hits. + + * "git log --first-parent $pathspec" did not stay on the first parent + chain and veered into side branch from which the whole change to the + specified paths came. + + * "git merge --no-edit $tag" failed to honor the --no-edit option. + + * "git merge --ff-only $tag" failed because it cannot record the + required mergetag without creating a merge, but this is so common + operation for branch that is used _only_ to follow the upstream, so + it was changed to allow fast-forwarding without recording the mergetag. + + * "git mergetool" now gives an empty file as the common base version + to the backend when dealing with the "both sides added, differently" + case. + + * "git push -q" was not sufficiently quiet. + + * When "git push" fails to update any refs, the client side did not + report an error correctly to the end user. + + * "rebase" and "commit --amend" failed to work on commits with ancient + timestamps near year 1970. + + * When asking for a tag to be pulled, "request-pull" did not show the + name of the tag prefixed with "tags/", which would have helped older + clients. + + * "git submodule add $path" forgot to recompute the name to be stored + in .gitmodules when the submodule at $path was once added to the + superproject and already initialized. + + * Many small corner case bugs on "git tag -n" was corrected. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.9.2.txt b/Documentation/RelNotes/1.7.9.2.txt new file mode 100644 index 000000000..e500da75d --- /dev/null +++ b/Documentation/RelNotes/1.7.9.2.txt @@ -0,0 +1,69 @@ +Git v1.7.9.2 Release Notes +========================== + +Fixes since v1.7.9.1 +-------------------- + + * Bash completion script (in contrib/) did not like a pattern that + begins with a dash to be passed to __git_ps1 helper function. + + * Adaptation of the bash completion script (in contrib/) for zsh + incorrectly listed all subcommands when "git " was given + to ask for list of porcelain subcommands. + + * The build procedure for profile-directed optimized binary was not + working very well. + + * Some systems need to explicitly link -lcharset to get locale_charset(). + + * t5541 ignored user-supplied port number used for HTTP server testing. + + * The error message emitted when we see an empty loose object was + not phrased correctly. + + * The code to ask for password did not fall back to the terminal + input when GIT_ASKPASS is set but does not work (e.g. lack of X + with GUI askpass helper). + + * We failed to give the true terminal width to any subcommand when + they are invoked with the pager, i.e. "git -p cmd". + + * map_user() was not rewriting its output correctly, which resulted + in the user visible symptom that "git blame -e" sometimes showed + excess '>' at the end of email addresses. + + * "git checkout -b" did not allow switching out of an unborn branch. + + * When you have both .../foo and .../foo.git, "git clone .../foo" did not + favor the former but the latter. + + * "git commit" refused to create a commit when entries added with + "add -N" remained in the index, without telling Git what their content + in the next commit should be. We should have created the commit without + these paths. + + * "git diff --stat" said "files", "insertions", and "deletions" even + when it is showing one "file", one "insertion" or one "deletion". + + * The output from "git diff --stat" for two paths that have the same + amount of changes showed graph bars of different length due to the + way we handled rounding errors. + + * "git grep" did not pay attention to -diff (hence -binary) attribute. + + * The transport programs (fetch, push, clone)ignored --no-progress + and showed progress when sending their output to a terminal. + + * Sometimes error status detected by a check in an earlier phase of + "git receive-pack" (the other end of "git push") was lost by later + checks, resulting in false indication of success. + + * "git rev-list --verify" sometimes skipped verification depending on + the phase of the moon, which dates back to 1.7.8.x series. + + * Search box in "gitweb" did not accept non-ASCII characters correctly. + + * Search interface of "gitweb" did not show multiple matches in the same file + correctly. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.9.3.txt b/Documentation/RelNotes/1.7.9.3.txt new file mode 100644 index 000000000..91c65012f --- /dev/null +++ b/Documentation/RelNotes/1.7.9.3.txt @@ -0,0 +1,51 @@ +Git v1.7.9.3 Release Notes +========================== + +Fixes since v1.7.9.2 +-------------------- + + * "git p4" (in contrib/) submit the changes to a wrong place when the + "--use-client-spec" option is set. + + * The config.mak.autogen generated by optional autoconf support tried + to link the binary with -lintl even when libintl.h is missing from + the system. + + * When the filter driver exits before reading the content before the + main git process writes the contents to be filtered to the pipe to + it, the latter could be killed with SIGPIPE instead of ignoring + such an event as an error. + + * "git add --refresh " used to warn about unmerged paths + outside the given pathspec. + + * The bulk check-in codepath in "git add" streamed contents that + needs smudge/clean filters without running them, instead of punting + and delegating to the codepath to run filters after slurping + everything to core. + + * "git branch --with $that" assumed incorrectly that the user will never + ask the question with nonsense value in $that. + + * "git bundle create" produced a corrupt bundle file upon seeing + commits with excessively long subject line. + + * When a remote helper exits before reading the blank line from the + main git process to signal the end of commands, the latter could be + killed with SIGPIPE. Instead we should ignore such event as a + non-error. + + * The commit log template given with "git merge --edit" did not have + a short instructive text like what "git commit" gives. + + * "git rev-list --verify-objects -q" omitted the extra verification + it needs to do over "git rev-list --objects -q" by mistake. + + * "gitweb" used to drop warnings in the log file when "heads" view is + accessed in a repository whose HEAD does not point at a valid + branch. + + * An invalid regular expression pattern given by an end user made + "gitweb" to return garbled response. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.9.4.txt b/Documentation/RelNotes/1.7.9.4.txt new file mode 100644 index 000000000..e5217a188 --- /dev/null +++ b/Documentation/RelNotes/1.7.9.4.txt @@ -0,0 +1,24 @@ +Git v1.7.9.4 Release Notes +========================== + +Fixes since v1.7.9.3 +-------------------- + + * The code to synthesize the fake ancestor tree used by 3-way merge + fallback in "git am" was not prepared to read a patch created with + a non-standard -p value. + + * "git bundle" did not record boundary commits correctly when there + are many of them. + + * "git diff-index" and its friends at the plumbing level showed the + "diff --git" header and nothing else for a path whose cached stat + info is dirty without actual difference when asked to produce a + patch. This was a longstanding bug that we could have fixed long + time ago. + + * "gitweb" did use quotemeta() to prepare search string when asked to + do a fixed-string project search, but did not use it by mistake and + used the user-supplied string instead. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.9.5.txt b/Documentation/RelNotes/1.7.9.5.txt new file mode 100644 index 000000000..95cc2bbf2 --- /dev/null +++ b/Documentation/RelNotes/1.7.9.5.txt @@ -0,0 +1,23 @@ +Git v1.7.9.5 Release Notes +========================== + +Fixes since v1.7.9.4 +-------------------- + + * When "git config" diagnoses an error in a configuration file and + shows the line number for the offending line, it miscounted if the + error was at the end of line. + + * "git fast-import" accepted "ls" command with an empty path by + mistake. + + * Various new-ish output decoration modes of "git grep" were not + documented in the manual's synopsis section. + + * The "remaining" subcommand to "git rerere" was not documented. + + * "gitweb" used to drop warnings in the log file when "heads" view is + accessed in a repository whose HEAD does not point at a valid + branch. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.9.6.txt b/Documentation/RelNotes/1.7.9.6.txt new file mode 100644 index 000000000..74bf8825e --- /dev/null +++ b/Documentation/RelNotes/1.7.9.6.txt @@ -0,0 +1,12 @@ +Git v1.7.9.6 Release Notes +========================== + +Fixes since v1.7.9.5 +-------------------- + + * "git merge $tag" to merge an annotated tag always opens the editor + during an interactive edit session. v1.7.10 series introduced an + environment variable GIT_MERGE_AUTOEDIT to help older scripts decline + this behaviour, but the maintenance track should also support it. + +Also contains minor fixes and documentation updates. diff --git a/Documentation/RelNotes/1.7.9.txt b/Documentation/RelNotes/1.7.9.txt new file mode 100644 index 000000000..95320aad5 --- /dev/null +++ b/Documentation/RelNotes/1.7.9.txt @@ -0,0 +1,112 @@ +Git v1.7.9 Release Notes +======================== + +Updates since v1.7.8 +-------------------- + + * gitk updates accumulated since early 2011. + + * git-gui updated to 0.16.0. + + * git-p4 (in contrib/) updates. + + * Git uses gettext to translate its most common interface messages + into the user's language if translations are available and the + locale is appropriately set. Distributors can drop new PO files + in po/ to add new translations. + + * The code to handle username/password for HTTP transactions used in + "git push" & "git fetch" learned to talk "credential API" to + external programs to cache or store them, to allow integration with + platform native keychain mechanisms. + + * The input prompts in the terminal use our own getpass() replacement + when possible. HTTP transactions used to ask for the username without + echoing back what was typed, but with this change you will see it as + you type. + + * The internals of "revert/cherry-pick" have been tweaked to prepare + building more generic "sequencer" on top of the implementation that + drives them. + + * "git rev-parse FETCH_HEAD" after "git fetch" without specifying + what to fetch from the command line will now show the commit that + would be merged if the command were "git pull". + + * "git add" learned to stream large files directly into a packfile + instead of writing them into individual loose object files. + + * "git checkout -B " is a more intuitive + way to spell "git reset --keep ". + + * "git checkout" and "git merge" learned "--no-overwrite-ignore" option + to tell Git that untracked and ignored files are not expendable. + + * "git commit --amend" learned "--no-edit" option to say that the + user is amending the tree being recorded, without updating the + commit log message. + + * "git commit" and "git reset" re-learned the optimization to prime + the cache-tree information in the index, which makes it faster to + write a tree object out after the index entries are updated. + + * "git commit" detects and rejects an attempt to stuff NUL byte in + the commit log message. + + * "git commit" learned "-S" to GPG-sign the commit; this can be shown + with the "--show-signature" option to "git log". + + * fsck and prune are relatively lengthy operations that still go + silent while making the end-user wait. They learned to give progress + output like other slow operations. + + * The set of built-in function-header patterns for various languages + knows MATLAB. + + * "git log --format=''" learned new %g[nNeE] specifiers to + show information from the reflog entries when walking the reflog + (i.e. with "-g"). + + * "git pull" can be used to fetch and merge an annotated/signed tag, + instead of the tip of a topic branch. The GPG signature from the + signed tag is recorded in the resulting merge commit for later + auditing. + + * "git log" learned "--show-signature" option to show the signed tag + that was merged that is embedded in the merge commit. It also can + show the signature made on the commit with "git commit -S". + + * "git branch --edit-description" can be used to add descriptive text + to explain what a topic branch is about. + + * "git fmt-merge-msg" learned to take the branch description into + account when preparing a merge summary that "git merge" records + when merging a local branch. + + * "git request-pull" has been updated to convey more information + useful for integrators to decide if a topic is worth merging and + what is pulled is indeed what the requestor asked to pull, + including: + + - the tip of the branch being requested to be merged; + - the branch description describing what the topic is about; + - the contents of the annotated tag, when requesting to pull a tag. + + * "git pull" learned to notice 'pull.rebase' configuration variable, + which serves as a global fallback for setting 'branch..rebase' + configuration variable per branch. + + * "git tag" learned "--cleanup" option to control how the whitespaces + and empty lines in tag message are cleaned up. + + * "gitweb" learned to show side-by-side diff. + +Also contains minor documentation updates and code clean-ups. + + +Fixes since v1.7.8 +------------------ + +Unless otherwise noted, all the fixes since v1.7.8 in the maintenance +releases are contained in this release (see release notes to them for +details). diff --git a/Documentation/config.txt b/Documentation/config.txt index 9fba453f2..0e1168c06 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -12,8 +12,9 @@ The configuration variables are used by both the git plumbing and the porcelains. The variables are divided into sections, wherein the fully qualified variable name of the variable itself is the last dot-separated segment and the section name is everything before the last -dot. The variable names are case-insensitive and only alphanumeric -characters are allowed. Some variables may appear multiple times. +dot. The variable names are case-insensitive, allow only alphanumeric +characters and `-`, and must start with an alphabetic character. Some +variables may appear multiple times. Syntax ~~~~~~ @@ -54,9 +55,10 @@ All the other lines (and the remainder of the line after the section header) are recognized as setting variables, in the form 'name = value'. If there is no equal sign on the line, the entire line is taken as 'name' and the variable is recognized as boolean "true". -The variable names are case-insensitive and only alphanumeric -characters and `-` are allowed. There can be more than one value -for a given variable; we say then that variable is multivalued. +The variable names are case-insensitive, allow only alphanumeric characters +and `-`, and must start with an alphabetic character. There can be more +than one value for a given variable; we say then that the variable is +multivalued. Leading and trailing whitespace in a variable value is discarded. Internal whitespace within a variable value is retained verbatim. @@ -674,10 +676,12 @@ branch..mergeoptions:: branch..rebase:: When true, rebase the branch on top of the fetched branch, instead of merging the default branch from the default remote when - "git pull" is run. - *NOTE*: this is a possibly dangerous operation; do *not* use - it unless you understand the implications (see linkgit:git-rebase[1] - for details). + "git pull" is run. See "pull.rebase" for doing this in a non + branch-specific manner. ++ +*NOTE*: this is a possibly dangerous operation; do *not* use +it unless you understand the implications (see linkgit:git-rebase[1] +for details). browser..cmd:: Specify the command to invoke the specified browser. The @@ -829,6 +833,29 @@ commit.template:: "{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the specified user's home directory. +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. + +credential.useHttpPath:: + When acquiring credentials, consider the "path" component of an http + or https URL to be important. Defaults to false. See + linkgit:gitcredentials[7] for more information. + +credential.username:: + If no username is set for a network authentication, use this username + by default. See credential..* below, and + linkgit:gitcredentials[7]. + +credential..*:: + Any of the credential.* options above can be applied selectively to + some credentials. For example "credential.https://example.com.username" + would set the default username only for https connections to + example.com. See linkgit:gitcredentials[7] for details on how URLs are + matched. + include::diff-config.txt[] difftool..path:: @@ -1098,6 +1125,17 @@ grep.lineNumber:: grep.extendedRegexp:: If set to true, enable '--extended-regexp' option by default. +gpg.program:: + Use this custom program instead of "gpg" found on $PATH when + making or verifying a PGP signature. The program must support the + same command line interface as GPG, namely, to verify a detached + signature, "gpg --verify $file - <$signature" is run, and the + program is expected to signal a good signature by exiting with + code 0, and to generate an ascii-armored detached signature, the + standard input of "gpg -bsau $key" is fed with the contents to be + signed, and the program is expected to send the result to its + standard output. + gui.commitmsgwidth:: Defines how wide the commit message window is in the linkgit:git-gui[1]. "75" is the default. @@ -1222,9 +1260,10 @@ help.autocorrect:: This is the default. http.proxy:: - Override the HTTP proxy, normally configured using the 'http_proxy' - environment variable (see linkgit:curl[1]). This can be overridden - on a per-remote basis; see remote..proxy + Override the HTTP proxy, normally configured using the 'http_proxy', + 'https_proxy', and 'all_proxy' environment variables (see + `curl(1)`). This can be overridden on a per-remote basis; see + remote..proxy http.cookiefile:: File containing previously stored cookie lines which should be used @@ -1587,6 +1626,16 @@ pretty.:: Note that an alias with the same name as a built-in format will be silently ignored. +pull.rebase:: + When true, rebase branches on top of the fetched branch, instead + of merging the default branch from the default remote when "git + pull" is run. See "branch..rebase" for setting this on a + per-branch basis. ++ +*NOTE*: this is a possibly dangerous operation; do *not* use +it unless you understand the implications (see linkgit:git-rebase[1] +for details). + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index f46013c91..0427e80a3 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -14,6 +14,7 @@ SYNOPSIS 'git branch' [--set-upstream | --track | --no-track] [-l] [-f] [] 'git branch' (-m | -M) [] 'git branch' (-d | -D) [-r] ... +'git branch' --edit-description [] DESCRIPTION ----------- @@ -158,6 +159,10 @@ start-point is either a local or remote-tracking branch. like '--track' would when creating the branch, except that where branch points to is not changed. +--edit-description:: + Open an editor and edit the text to explain what the branch is + for, to be used by various other commands (e.g. `request-pull`). + --contains :: Only list branches which contain the specified commit. diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index 02133d5fc..cfb9906bb 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -9,7 +9,8 @@ git-commit-tree - Create a new commit object SYNOPSIS -------- [verse] -'git commit-tree' [(-p )...] < changelog +'git commit-tree' [(-p )...] < changelog +'git commit-tree' [(-p )...] [(-m )...] [(-F )...] DESCRIPTION ----------- @@ -17,7 +18,8 @@ This is usually not what an end user wants to run directly. See linkgit:git-commit[1] instead. Creates a new commit object based on the provided tree object and -emits the new commit object id on stdout. +emits the new commit object id on stdout. The log message is read +from the standard input, unless `-m` or `-F` options are given. A commit object may have any number of parents. With exactly one parent, it is an ordinary commit. Having more than one parent makes @@ -39,9 +41,17 @@ OPTIONS :: An existing tree object --p :: +-p :: Each '-p' indicates the id of a parent commit object. +-m :: + A paragraph in the commig log message. This can be given more than + once and each becomes its own paragraph. + +-F :: + Read the commit log message from the given file. Use `-` to read + from the standard input. + Commit Information ------------------ diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index e7ecf5d80..7617d9eb2 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -85,8 +85,11 @@ OPTIONS is not exactly one. --get-regexp:: - Like --get-all, but interprets the name as a regular expression. - Also outputs the key names. + Like --get-all, but interprets the name as a regular expression and + writes out the key names. Regular expression matching is currently + case-sensitive and done against a canonicalized version of the key + in which section and variable names are lowercased, but subsection + names are not. --global:: For writing options: write to global ~/.gitconfig file rather than diff --git a/Documentation/git-credential-cache--daemon.txt b/Documentation/git-credential-cache--daemon.txt new file mode 100644 index 000000000..11edc5a17 --- /dev/null +++ b/Documentation/git-credential-cache--daemon.txt @@ -0,0 +1,26 @@ +git-credential-cache--daemon(1) +=============================== + +NAME +---- +git-credential-cache--daemon - temporarily store user credentials in memory + +SYNOPSIS +-------- +[verse] +git credential-cache--daemon + +DESCRIPTION +----------- + +NOTE: You probably don't want to invoke this command yourself; it is +started automatically when you use linkgit:git-credential-cache[1]. + +This command listens on the Unix domain socket specified by `` +for `git-credential-cache` clients. Clients may store and retrieve +credentials. Each credential is held for a timeout specified by the +client; once no credentials are held, the daemon exits. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-credential-cache.txt b/Documentation/git-credential-cache.txt new file mode 100644 index 000000000..f3d09c5d5 --- /dev/null +++ b/Documentation/git-credential-cache.txt @@ -0,0 +1,77 @@ +git-credential-cache(1) +======================= + +NAME +---- +git-credential-cache - helper to temporarily store passwords in memory + +SYNOPSIS +-------- +----------------------------- +git config credential.helper 'cache [options]' +----------------------------- + +DESCRIPTION +----------- + +This command caches credentials in memory for use by future git +programs. The stored credentials never touch the disk, and are forgotten +after a configurable timeout. The cache is accessible over a Unix +domain socket, restricted to the current user by filesystem permissions. + +You probably don't want to invoke this command directly; it is meant to +be used as a credential helper by other parts of git. See +linkgit:gitcredentials[7] or `EXAMPLES` below. + +OPTIONS +------- + +--timeout :: + + Number of seconds to cache credentials (default: 900). + +--socket :: + + Use `` to contact a running cache daemon (or start a new + cache daemon if one is not started). Defaults to + `~/.git-credential-cache/socket`. If your home directory is on a + network-mounted filesystem, you may need to change this to a + local filesystem. + +CONTROLLING THE DAEMON +---------------------- + +If you would like the daemon to exit early, forgetting all cached +credentials before their timeout, you can issue an `exit` action: + +-------------------------------------- +git credential-cache exit +-------------------------------------- + +EXAMPLES +-------- + +The point of this helper is to reduce the number of times you must type +your username or password. For example: + +------------------------------------ +$ git config credential.helper cache +$ git push http://example.com/repo.git +Username: +Password: + +[work for 5 more minutes] +$ git push http://example.com/repo.git +[your credentials are used automatically] +------------------------------------ + +You can provide options via the credential.helper configuration +variable (this example drops the cache time to 5 minutes): + +------------------------------------------------------- +$ git config credential.helper 'cache --timeout=300' +------------------------------------------------------- + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-credential-store.txt b/Documentation/git-credential-store.txt new file mode 100644 index 000000000..31093467d --- /dev/null +++ b/Documentation/git-credential-store.txt @@ -0,0 +1,75 @@ +git-credential-store(1) +======================= + +NAME +---- +git-credential-store - helper to store credentials on disk + +SYNOPSIS +-------- +------------------- +git config credential.helper 'store [options]' +------------------- + +DESCRIPTION +----------- + +NOTE: Using this helper will store your passwords unencrypted on disk, +protected only by filesystem permissions. If this is not an acceptable +security tradeoff, try linkgit:git-credential-cache[1], or find a helper +that integrates with secure storage provided by your operating system. + +This command stores credentials indefinitely on disk for use by future +git programs. + +You probably don't want to invoke this command directly; it is meant to +be used as a credential helper by other parts of git. See +linkgit:gitcredentials[7] or `EXAMPLES` below. + +OPTIONS +------- + +--store=:: + + Use `` to store credentials. The file will have its + filesystem permissions set to prevent other users on the system + from reading it, but will not be encrypted or otherwise + protected. Defaults to `~/.git-credentials`. + +EXAMPLES +-------- + +The point of this helper is to reduce the number of times you must type +your username or password. For example: + +------------------------------------------ +$ git config credential.helper store +$ git push http://example.com/repo.git +Username: +Password: + +[several days later] +$ git push http://example.com/repo.git +[your credentials are used automatically] +------------------------------------------ + +STORAGE FORMAT +-------------- + +The `.git-credentials` file is stored in plaintext. Each credential is +stored on its own line as a URL like: + +------------------------------ +https://user:pass@example.com +------------------------------ + +When git needs authentication for a particular URL context, +credential-store will consider that context a pattern to match against +each entry in the credentials file. If the protocol, hostname, and +username (if we already have one) match, then the password is returned +to git. See the discussion of configuration in linkgit:gitcredentials[7] +for more information. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-fmt-merge-msg.txt b/Documentation/git-fmt-merge-msg.txt index 32aff954a..3a0f55ec8 100644 --- a/Documentation/git-fmt-merge-msg.txt +++ b/Documentation/git-fmt-merge-msg.txt @@ -53,6 +53,11 @@ OPTIONS CONFIGURATION ------------- +merge.branchdesc:: + In addition to branch names, populate the log message with + the branch description text associated with them. Defaults + to false. + merge.log:: In addition to branch names, populate the log message with at most the specified number of one-line descriptions from the diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index 55b33d703..6c47395ad 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs] - [--[no-]full] [--strict] [--verbose] [--lost-found] [*] + [--[no-]full] [--strict] [--verbose] [--lost-found] + [--[no-]progress] [*] DESCRIPTION ----------- @@ -72,6 +73,14 @@ index file, all SHA1 references in .git/refs/*, and all reflogs (unless a blob, the contents are written into the file, rather than its object name. +--progress:: +--no-progress:: + Progress status is reported on the standard error stream by + default when it is attached to a terminal, unless + --no-progress or --verbose is specified. --progress forces + progress status even if the standard error stream is not + directed to a terminal. + DISCUSSION ---------- diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 15d6711d4..343eadd40 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -20,7 +20,9 @@ SYNOPSIS [-c | --count] [--all-match] [-q | --quiet] [--max-depth ] [--color[=] | --no-color] + [--break] [--heading] [-p | --show-function] [-A ] [-B ] [-C ] + [-W | --function-context] [-f ] [-e] [--and|--or|--not|(|)|-e ...] [ [--exclude-standard] [--cached | --no-index | --untracked] | ...] @@ -79,6 +81,9 @@ OPTIONS --max-depth :: For each given on command line, descend at most levels of directories. A negative value means no limit. + This option is ignored if contains active wildcards. + In other words if "a*" matches a directory named "a*", + "*" is matched literally so --max-depth is still effective. -w:: --word-regexp:: diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 51dc32574..97e7a8e9e 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -25,13 +25,24 @@ command directly. See linkgit:git-am[1] instead. OPTIONS ------- -k:: - Usually the program 'cleans up' the Subject: header line - to extract the title line for the commit log message, - among which (1) remove 'Re:' or 're:', (2) leading - whitespaces, (3) '[' up to ']', typically '[PATCH]', and - then prepends "[PATCH] ". This flag forbids this - munging, and is most useful when used to read back - 'git format-patch -k' output. + Usually the program removes email cruft from the Subject: + header line to extract the title line for the commit log + message. This option prevents this munging, and is most + useful when used to read back 'git format-patch -k' output. ++ +Specifically, the following are removed until none of them remain: ++ +-- +* Leading and trailing whitespace. + +* Leading `Re:`, `re:`, and `:`. + +* Leading bracketed strings (between `[` and `]`, usually + `[PATCH]`). +-- ++ +Finally, runs of whitespace are normalized to a single ASCII space +character. -b:: When -k is not in effect, all leading strings bracketed with '[' diff --git a/Documentation/git-p4.txt b/Documentation/git-p4.txt new file mode 100644 index 000000000..ed827902f --- /dev/null +++ b/Documentation/git-p4.txt @@ -0,0 +1,497 @@ +git-p4(1) +========= + +NAME +---- +git-p4 - Import from and submit to Perforce repositories + + +SYNOPSIS +-------- +[verse] +'git p4 clone' [] [] ... +'git p4 sync' [] [...] +'git p4 rebase' +'git p4 submit' [] [] + + +DESCRIPTION +----------- +This command provides a way to interact with p4 repositories +using git. + +Create a new git repository from an existing p4 repository using +'git p4 clone', giving it one or more p4 depot paths. Incorporate +new commits from p4 changes with 'git p4 sync'. The 'sync' command +is also used to include new branches from other p4 depot paths. +Submit git changes back to p4 using 'git p4 submit'. The command +'git p4 rebase' does a sync plus rebases the current branch onto +the updated p4 remote branch. + + +EXAMPLE +------- +* Create an alias for 'git p4', using the full path to the 'git-p4' + script if needed: ++ +------------ +$ git config --global alias.p4 '!git-p4' +------------ + +* Clone a repository: ++ +------------ +$ git p4 clone //depot/path/project +------------ + +* Do some work in the newly created git repository: ++ +------------ +$ cd project +$ vi foo.h +$ git commit -a -m "edited foo.h" +------------ + +* Update the git repository with recent changes from p4, rebasing your + work on top: ++ +------------ +$ git p4 rebase +------------ + +* Submit your commits back to p4: ++ +------------ +$ git p4 submit +------------ + + +COMMANDS +-------- + +Clone +~~~~~ +Generally, 'git p4 clone' is used to create a new git directory +from an existing p4 repository: +------------ +$ git p4 clone //depot/path/project +------------ +This: + +1. Creates an empty git repository in a subdirectory called 'project'. ++ +2. Imports the full contents of the head revision from the given p4 +depot path into a single commit in the git branch 'refs/remotes/p4/master'. ++ +3. Creates a local branch, 'master' from this remote and checks it out. + +To reproduce the entire p4 history in git, use the '@all' modifier on +the depot path: +------------ +$ git p4 clone //depot/path/project@all +------------ + + +Sync +~~~~ +As development continues in the p4 repository, those changes can +be included in the git repository using: +------------ +$ git p4 sync +------------ +This command finds new changes in p4 and imports them as git commits. + +P4 repositories can be added to an existing git repository using +'git p4 sync' too: +------------ +$ mkdir repo-git +$ cd repo-git +$ git init +$ git p4 sync //path/in/your/perforce/depot +------------ +This imports the specified depot into +'refs/remotes/p4/master' in an existing git repository. The +'--branch' option can be used to specify a different branch to +be used for the p4 content. + +If a git repository includes branches 'refs/remotes/origin/p4', these +will be fetched and consulted first during a 'git p4 sync'. Since +importing directly from p4 is considerably slower than pulling changes +from a git remote, this can be useful in a multi-developer environment. + + +Rebase +~~~~~~ +A common working pattern is to fetch the latest changes from the p4 depot +and merge them with local uncommitted changes. Often, the p4 repository +is the ultimate location for all code, thus a rebase workflow makes +sense. This command does 'git p4 sync' followed by 'git rebase' to move +local commits on top of updated p4 changes. +------------ +$ git p4 rebase +------------ + + +Submit +~~~~~~ +Submitting changes from a git repository back to the p4 repository +requires a separate p4 client workspace. This should be specified +using the 'P4CLIENT' environment variable or the git configuration +variable 'git-p4.client'. The p4 client must exist, but the client root +will be created and populated if it does not already exist. + +To submit all changes that are in the current git branch but not in +the 'p4/master' branch, use: +------------ +$ git p4 submit +------------ + +To specify a branch other than the current one, use: +------------ +$ git p4 submit topicbranch +------------ + +The upstream reference is generally 'refs/remotes/p4/master', but can +be overridden using the '--origin=' command-line option. + +The p4 changes will be created as the user invoking 'git p4 submit'. The +'--preserve-user' option will cause ownership to be modified +according to the author of the git commit. This option requires admin +privileges in p4, which can be granted using 'p4 protect'. + + +OPTIONS +------- + +General options +~~~~~~~~~~~~~~~ +All commands except clone accept this option. + +--git-dir :: + Set the 'GIT_DIR' environment variable. See linkgit:git[1]. + +Sync options +~~~~~~~~~~~~ +These options can be used in the initial 'clone' as well as in +subsequent 'sync' operations. + +--branch :: + Import changes into given branch. If the branch starts with + 'refs/', it will be used as is, otherwise the path 'refs/heads/' + will be prepended. The default branch is 'master'. If used + with an initial clone, no HEAD will be checked out. ++ +This example imports a new remote "p4/proj2" into an existing +git repository: +---- + $ git init + $ git p4 sync --branch=refs/remotes/p4/proj2 //depot/proj2 +---- + +--detect-branches:: + Use the branch detection algorithm to find new paths in p4. It is + documented below in "BRANCH DETECTION". + +--changesfile :: + Import exactly the p4 change numbers listed in 'file', one per + line. Normally, 'git p4' inspects the current p4 repository + state and detects the changes it should import. + +--silent:: + Do not print any progress information. + +--verbose:: + Provide more progress information. + +--detect-labels:: + Query p4 for labels associated with the depot paths, and add + them as tags in git. + +--import-local:: + By default, p4 branches are stored in 'refs/remotes/p4/', + where they will be treated as remote-tracking branches by + linkgit:git-branch[1] and other commands. This option instead + puts p4 branches in 'refs/heads/p4/'. Note that future + sync operations must specify '--import-local' as well so that + they can find the p4 branches in refs/heads. + +--max-changes :: + Limit the number of imported changes to 'n'. Useful to + limit the amount of history when using the '@all' p4 revision + specifier. + +--keep-path:: + The mapping of file names from the p4 depot path to git, by + default, involves removing the entire depot path. With this + option, the full p4 depot path is retained in git. For example, + path '//depot/main/foo/bar.c', when imported from + '//depot/main/', becomes 'foo/bar.c'. With '--keep-path', the + git path is instead 'depot/main/foo/bar.c'. + +--use-client-spec:: + Use a client spec to find the list of interesting files in p4. + See the "CLIENT SPEC" section below. + +Clone options +~~~~~~~~~~~~~ +These options can be used in an initial 'clone', along with the 'sync' +options described above. + +--destination :: + Where to create the git repository. If not provided, the last + component in the p4 depot path is used to create a new + directory. + +--bare:: + Perform a bare clone. See linkgit:git-clone[1]. + +-/ :: + Exclude selected depot paths when cloning. + +Submit options +~~~~~~~~~~~~~~ +These options can be used to modify 'git p4 submit' behavior. + +--verbose:: + Provide more progress information. + +--origin :: + Upstream location from which commits are identified to submit to + p4. By default, this is the most recent p4 commit reachable + from 'HEAD'. + +-M[]:: + Detect renames. See linkgit:git-diff[1]. Renames will be + represented in p4 using explicit 'move' operations. There + is no corresponding option to detect copies, but there are + variables for both moves and copies. + +--preserve-user:: + Re-author p4 changes before submitting to p4. This option + requires p4 admin privileges. + + +DEPOT PATH SYNTAX +----------------- +The p4 depot path argument to 'git p4 sync' and 'git p4 clone' can +be one or more space-separated p4 depot paths, with an optional +p4 revision specifier on the end: + +"//depot/my/project":: + Import one commit with all files in the '#head' change under that tree. + +"//depot/my/project@all":: + Import one commit for each change in the history of that depot path. + +"//depot/my/project@1,6":: + Import only changes 1 through 6. + +"//depot/proj1@all //depot/proj2@all":: + Import all changes from both named depot paths into a single + repository. Only files below these directories are included. + There is not a subdirectory in git for each "proj1" and "proj2". + You must use the '--destination' option when specifying more + than one depot path. The revision specifier must be specified + identically on each depot path. If there are files in the + depot paths with the same name, the path with the most recently + updated version of the file is the one that appears in git. + +See 'p4 help revisions' for the full syntax of p4 revision specifiers. + + +CLIENT SPEC +----------- +The p4 client specification is maintained with the 'p4 client' command +and contains among other fields, a View that specifies how the depot +is mapped into the client repository. The 'clone' and 'sync' commands +can consult the client spec when given the '--use-client-spec' option or +when the useClientSpec variable is true. After 'git p4 clone', the +useClientSpec variable is automatically set in the repository +configuration file. This allows future 'git p4 submit' commands to +work properly; the submit command looks only at the variable and does +not have a command-line option. + +The full syntax for a p4 view is documented in 'p4 help views'. Git-p4 +knows only a subset of the view syntax. It understands multi-line +mappings, overlays with '+', exclusions with '-' and double-quotes +around whitespace. Of the possible wildcards, git-p4 only handles +'...', and only when it is at the end of the path. Git-p4 will complain +if it encounters an unhandled wildcard. + +The name of the client can be given to git-p4 in multiple ways. The +variable 'git-p4.client' takes precedence if it exists. Otherwise, +normal p4 mechanisms of determining the client are used: environment +variable P4CLIENT, a file referenced by P4CONFIG, or the local host name. + + +BRANCH DETECTION +---------------- +P4 does not have the same concept of a branch as git. Instead, +p4 organizes its content as a directory tree, where by convention +different logical branches are in different locations in the tree. +The 'p4 branch' command is used to maintain mappings between +different areas in the tree, and indicate related content. 'git p4' +can use these mappings to determine branch relationships. + +If you have a repository where all the branches of interest exist as +subdirectories of a single depot path, you can use '--detect-branches' +when cloning or syncing to have 'git p4' automatically find +subdirectories in p4, and to generate these as branches in git. + +For example, if the P4 repository structure is: +---- +//depot/main/... +//depot/branch1/... +---- + +And "p4 branch -o branch1" shows a View line that looks like: +---- +//depot/main/... //depot/branch1/... +---- + +Then this 'git p4 clone' command: +---- +git p4 clone --detect-branches //depot@all +---- +produces a separate branch in 'refs/remotes/p4/' for //depot/main, +called 'master', and one for //depot/branch1 called 'depot/branch1'. + +However, it is not necessary to create branches in p4 to be able to use +them like branches. Because it is difficult to infer branch +relationships automatically, a git configuration setting +'git-p4.branchList' can be used to explicitly identify branch +relationships. It is a list of "source:destination" pairs, like a +simple p4 branch specification, where the "source" and "destination" are +the path elements in the p4 repository. The example above relied on the +presence of the p4 branch. Without p4 branches, the same result will +occur with: +---- +git config git-p4.branchList main:branch1 +git p4 clone --detect-branches //depot@all +---- + + +PERFORMANCE +----------- +The fast-import mechanism used by 'git p4' creates one pack file for +each invocation of 'git p4 sync'. Normally, git garbage compression +(linkgit:git-gc[1]) automatically compresses these to fewer pack files, +but explicit invocation of 'git repack -adf' may improve performance. + + +CONFIGURATION VARIABLES +----------------------- +The following config settings can be used to modify 'git p4' behavior. +They all are in the 'git-p4' section. + +General variables +~~~~~~~~~~~~~~~~~ +git-p4.user:: + User specified as an option to all p4 commands, with '-u '. + The environment variable 'P4USER' can be used instead. + +git-p4.password:: + Password specified as an option to all p4 commands, with + '-P '. + The environment variable 'P4PASS' can be used instead. + +git-p4.port:: + Port specified as an option to all p4 commands, with + '-p '. + The environment variable 'P4PORT' can be used instead. + +git-p4.host:: + Host specified as an option to all p4 commands, with + '-h '. + The environment variable 'P4HOST' can be used instead. + +git-p4.client:: + Client specified as an option to all p4 commands, with + '-c ', including the client spec. + +Clone and sync variables +~~~~~~~~~~~~~~~~~~~~~~~~ +git-p4.syncFromOrigin:: + Because importing commits from other git repositories is much faster + than importing them from p4, a mechanism exists to find p4 changes + first in git remotes. If branches exist under 'refs/remote/origin/p4', + those will be fetched and used when syncing from p4. This + variable can be set to 'false' to disable this behavior. + +git-p4.branchUser:: + One phase in branch detection involves looking at p4 branches + to find new ones to import. By default, all branches are + inspected. This option limits the search to just those owned + by the single user named in the variable. + +git-p4.branchList:: + List of branches to be imported when branch detection is + enabled. Each entry should be a pair of branch names separated + by a colon (:). This example declares that both branchA and + branchB were created from main: +------------- +git config git-p4.branchList main:branchA +git config --add git-p4.branchList main:branchB +------------- + +git-p4.useClientSpec:: + Specify that the p4 client spec should be used to identify p4 + depot paths of interest. This is equivalent to specifying the + option '--use-client-spec'. See the "CLIENT SPEC" section above. + This variable is a boolean, not the name of a p4 client. + +Submit variables +~~~~~~~~~~~~~~~~ +git-p4.detectRenames:: + Detect renames. See linkgit:git-diff[1]. + +git-p4.detectCopies:: + Detect copies. See linkgit:git-diff[1]. + +git-p4.detectCopiesHarder:: + Detect copies harder. See linkgit:git-diff[1]. + +git-p4.preserveUser:: + On submit, re-author changes to reflect the git author, + regardless of who invokes 'git p4 submit'. + +git-p4.allowMissingP4Users:: + When 'preserveUser' is true, 'git p4' normally dies if it + cannot find an author in the p4 user map. This setting + submits the change regardless. + +git-p4.skipSubmitEdit:: + The submit process invokes the editor before each p4 change + is submitted. If this setting is true, though, the editing + step is skipped. + +git-p4.skipSubmitEditCheck:: + After editing the p4 change message, 'git p4' makes sure that + the description really was changed by looking at the file + modification time. This option disables that test. + +git-p4.allowSubmit:: + By default, any branch can be used as the source for a 'git p4 + submit' operation. This configuration variable, if set, permits only + the named branches to be used as submit sources. Branch names + must be the short names (no "refs/heads/"), and should be + separated by commas (","), with no spaces. + +git-p4.skipUserNameCheck:: + If the user running 'git p4 submit' does not exist in the p4 + user map, 'git p4' exits. This option can be used to force + submission regardless. + + +IMPLEMENTATION DETAILS +---------------------- +* Changesets from p4 are imported using git fast-import. +* Cloning or syncing does not require a p4 client; file contents are + collected using 'p4 print'. +* Submitting requires a p4 client, which is not in the same location + as the git repository. Patches are applied, one at a time, to + this p4 client and submitted from there. +* Each commit imported by 'git p4' has a line at the end of the log + message indicating the p4 depot location and change number. This + line is used by later 'git p4 sync' operations to know which p4 + changes are new. diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index e1da46876..0f18ec891 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -108,7 +108,7 @@ include::merge-options.txt[] fetched, the rebase uses that information to avoid rebasing non-local changes. + -See `branch..rebase` and `branch.autosetuprebase` in +See `pull.rebase`, `branch..rebase` and `branch.autosetuprebase` in linkgit:git-config[1] if you want to make `git pull` always use `{litdd}rebase` instead of merging. + diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index a43e87448..c4bde6509 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -341,7 +341,7 @@ since you pulled from him: ---------------- $ git fetch git://.... linus -$ LT=`cat .git/FETCH_HEAD` +$ LT=`git rev-parse FETCH_HEAD` ---------------- Your work tree is still based on your HEAD ($JC), but you have diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 504945c69..520aaa94f 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -409,10 +409,13 @@ The interactive mode is meant for this type of workflow: where point 2. consists of several instances of -a. regular use +a) regular use + 1. finish something worthy of a commit 2. commit -b. independent fixup + +b) independent fixup + 1. realize that something does not work 2. fix that 3. commit it diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 5a8c5061f..d376d19ef 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -14,7 +14,7 @@ SYNOPSIS 'git remote rename' 'git remote rm' 'git remote set-head' (-a | -d | ) -'git remote set-branches' [--add] ... +'git remote set-branches' [--add] ... 'git remote set-url' [--push] [] 'git remote set-url --add' [--push] 'git remote set-url --delete' [--push] diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt index 3c4589529..fcee0008a 100644 --- a/Documentation/git-show-ref.txt +++ b/Documentation/git-show-ref.txt @@ -44,7 +44,7 @@ OPTIONS -d:: --dereference:: - Dereference tags into object IDs as well. They will be shown with "^{}" + Dereference tags into object IDs as well. They will be shown with "{caret}{}" appended. -s:: @@ -73,9 +73,9 @@ OPTIONS --exclude-existing[=]:: Make 'git show-ref' act as a filter that reads refs from stdin of the - form "^(?:\s)?(?:{backslash}{caret}\{\})?$" + form "`{caret}(?:\s)?(?:{backslash}{caret}{})?$`" and performs the following actions on each: - (1) strip "^{}" at the end of line if any; + (1) strip "{caret}{}" at the end of line if any; (2) ignore if pattern is provided and does not head-match refname; (3) warn if refname is not a well-formed refname and skip; (4) ignore if refname is a ref that exists in the local repository; diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index c83cb13de..53ff5f6cf 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -38,7 +38,9 @@ created (i.e. a lightweight tag). A GnuPG signed tag object will be created when `-s` or `-u ` is used. When `-u ` is not used, the committer identity for the current user is used to find the -GnuPG key for signing. +GnuPG key for signing. The configuration variable `gpg.program` +is used to specify custom GnuPG binary. + OPTIONS ------- @@ -48,11 +50,11 @@ OPTIONS -s:: --sign:: - Make a GPG-signed tag, using the default e-mail address's key + Make a GPG-signed tag, using the default e-mail address's key. -u :: --local-user=:: - Make a GPG-signed tag, using the given key + Make a GPG-signed tag, using the given key. -f:: --force:: @@ -99,6 +101,13 @@ OPTIONS Implies `-a` if none of `-a`, `-s`, or `-u ` is given. +--cleanup=:: + This option sets how the tag message is cleaned up. + The '' can be one of 'verbatim', 'whitespace' and 'strip'. The + 'strip' mode is default. The 'verbatim' mode does not change message at + all, 'whitespace' removes just leading/trailing whitespace lines and + 'strip' removes both whitespace and commentary. + :: The name of the tag to create, delete, or describe. The new tag name must pass all checks defined by diff --git a/Documentation/git.txt b/Documentation/git.txt index 614693a83..ae34e8a7f 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -9,11 +9,11 @@ git - the stupid content tracker SYNOPSIS -------- [verse] -'git' [--version] [--exec-path[=]] [--html-path] [--man-path] [--info-path] +'git' [--version] [--help] [-c =] + [--exec-path[=]] [--html-path] [--man-path] [--info-path] [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] [--git-dir=] [--work-tree=] [--namespace=] - [-c =] - [--help] [] + [] DESCRIPTION ----------- @@ -44,9 +44,21 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.7.8.4/git.html[documentation for release 1.7.8.4] +* link:v1.7.9.6/git.html[documentation for release 1.7.9.6] * release notes for + link:RelNotes/1.7.9.6.txt[1.7.9.6], + link:RelNotes/1.7.9.5.txt[1.7.9.5], + link:RelNotes/1.7.9.4.txt[1.7.9.4], + link:RelNotes/1.7.9.3.txt[1.7.9.3], + link:RelNotes/1.7.9.2.txt[1.7.9.2], + link:RelNotes/1.7.9.1.txt[1.7.9.1], + link:RelNotes/1.7.9.txt[1.7.9]. + +* link:v1.7.8.5/git.html[documentation for release 1.7.8.5] + +* release notes for + link:RelNotes/1.7.8.5.txt[1.7.8.5], link:RelNotes/1.7.8.4.txt[1.7.8.4], link:RelNotes/1.7.8.3.txt[1.7.8.3], link:RelNotes/1.7.8.2.txt[1.7.8.2], @@ -702,6 +714,12 @@ other a pager. See also the `core.pager` option in linkgit:git-config[1]. +'GIT_EDITOR':: + This environment variable overrides `$EDITOR` and `$VISUAL`. + It is used by several git comands when, on interactive mode, + an editor is to be launched. See also linkgit:git-var[1] + and the `core.editor` option in linkgit:git-config[1]. + 'GIT_SSH':: If this environment variable is set then 'git fetch' and 'git push' will use this command instead diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 25e46aeb7..a85b187e0 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -500,6 +500,8 @@ patterns are available: - `java` suitable for source code in the Java language. +- `matlab` suitable for source code in the MATLAB language. + - `objc` suitable for source code in the Objective-C language. - `pascal` suitable for source code in the Pascal/Delphi language. diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index c27d086f6..fb0d5692a 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -1004,7 +1004,7 @@ Updating from ae3a2da... to a80b4aa.... Fast-forward (no commit created; -m option ignored) example | 1 + hello | 1 + - 2 files changed, 2 insertions(+), 0 deletions(-) + 2 files changed, 2 insertions(+) ---------------- Because your branch did not contain anything more than what had diff --git a/Documentation/gitcredentials.txt b/Documentation/gitcredentials.txt new file mode 100644 index 000000000..066f825f2 --- /dev/null +++ b/Documentation/gitcredentials.txt @@ -0,0 +1,183 @@ +gitcredentials(7) +================= + +NAME +---- +gitcredentials - providing usernames and passwords to git + +SYNOPSIS +-------- +------------------ +git config credential.https://example.com.username myusername +git config credential.helper "$helper $options" +------------------ + +DESCRIPTION +----------- + +Git will sometimes need credentials from the user in order to perform +operations; for example, it may need to ask for a username and password +in order to access a remote repository over HTTP. This manual describes +the mechanisms git uses to request these credentials, as well as some +features to avoid inputting these credentials repeatedly. + +REQUESTING CREDENTIALS +---------------------- + +Without any credential helpers defined, git will try the following +strategies to ask the user for usernames and passwords: + +1. If the `GIT_ASKPASS` environment variable is set, the program + specified by the variable is invoked. A suitable prompt is provided + to the program on the command line, and the user's input is read + from its standard output. + +2. Otherwise, if the `core.askpass` configuration variable is set, its + value is used as above. + +3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its + value is used as above. + +4. Otherwise, the user is prompted on the terminal. + +AVOIDING REPETITION +------------------- + +It can be cumbersome to input the same credentials over and over. Git +provides two methods to reduce this annoyance: + +1. Static configuration of usernames for a given authentication context. + +2. Credential helpers to cache or store passwords, or to interact with + a system password wallet or keychain. + +The first is simple and appropriate if you do not have secure storage available +for a password. It is generally configured by adding this to your config: + +--------------------------------------- +[credential "https://example.com"] + username = me +--------------------------------------- + +Credential helpers, on the other hand, are external programs from which git can +request both usernames and passwords; they typically interface with secure +storage provided by the OS or other programs. + +To use a helper, you must first select one to use. Git currently +includes the following helpers: + +cache:: + + Cache credentials in memory for a short period of time. See + linkgit:git-credential-cache[1] for details. + +store:: + + Store credentials indefinitely on disk. See + linkgit:git-credential-store[1] for details. + +You may also have third-party helpers installed; search for +`credential-*` in the output of `git help -a`, and consult the +documentation of individual helpers. Once you have selected a helper, +you can tell git to use it by putting its name into the +credential.helper variable. + +1. Find a helper. ++ +------------------------------------------- +$ git help -a | grep credential- +credential-foo +------------------------------------------- + +2. Read its description. ++ +------------------------------------------- +$ git help credential-foo +------------------------------------------- + +3. Tell git to use it. ++ +------------------------------------------- +$ git config --global credential.helper foo +------------------------------------------- + +If there are multiple instances of the `credential.helper` configuration +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. + + +CREDENTIAL CONTEXTS +------------------- + +Git considers each credential to have a context defined by a URL. This context +is used to look up context-specific configuration, and is passed to any +helpers, which may use it as an index into secure storage. + +For instance, imagine we are accessing `https://example.com/foo.git`. When git +looks into a config file to see if a section matches this context, it will +consider the two a match if the context is a more-specific subset of the +pattern in the config file. For example, if you have this in your config file: + +-------------------------------------- +[credential "https://example.com"] + username = foo +-------------------------------------- + +then we will match: both protocols are the same, both hosts are the same, and +the "pattern" URL does not care about the path component at all. However, this +context would not match: + +-------------------------------------- +[credential "https://kernel.org"] + username = foo +-------------------------------------- + +because the hostnames differ. Nor would it match `foo.example.com`; git +compares hostnames exactly, without considering whether two hosts are part of +the same domain. Likewise, a config entry for `http://example.com` would not +match: git compares the protocols exactly. + + +CONFIGURATION OPTIONS +--------------------- + +Options for a credential context can be configured either in +`credential.\*` (which applies to all credentials), or +`credential..\*`, where matches the context as described +above. + +The following options are available in either location: + +helper:: + + The name of an external credential helper, and any associated options. + If the helper name is not an absolute path, then the string `git + credential-` is prepended. The resulting string is executed by the + shell (so, for example, setting this to `foo --option=bar` will execute + `git credential-foo --option=bar` via the shell. See the manual of + specific helpers for examples of their use. + +username:: + + A default username, if one is not provided in the URL. + +useHttpPath:: + + By default, git does not consider the "path" component of an http URL + to be worth matching via external helpers. This means that a credential + stored for `https://example.com/foo.git` will also be used for + `https://example.com/bar.git`. If you do want to distinguish these + cases, set this option to `true`. + + +CUSTOM HELPERS +-------------- + +You can write your own custom helpers to interface with any system in +which you keep credentials. See the documentation for git's +link:technical/api-credentials.html[credentials API] for details. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index f1e4422ac..e00a4d217 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -34,12 +34,12 @@ $ echo 'hello world' > file.txt $ git add . $ git commit -a -m "initial commit" [master (root-commit) 54196cc] initial commit - 1 files changed, 1 insertions(+), 0 deletions(-) + 1 file changed, 1 insertion(+) create mode 100644 file.txt $ echo 'hello world!' >file.txt $ git commit -a -m "add emphasis" [master c4d59f3] add emphasis - 1 files changed, 1 insertions(+), 1 deletions(-) + 1 file changed, 1 insertion(+), 1 deletion(-) ------------------------------------------------ What are the 7 digits of hex that git responded to the commit with? diff --git a/Documentation/howto/using-signed-tag-in-pull-request.txt b/Documentation/howto/using-signed-tag-in-pull-request.txt new file mode 100644 index 000000000..98c0033a5 --- /dev/null +++ b/Documentation/howto/using-signed-tag-in-pull-request.txt @@ -0,0 +1,217 @@ +From: Junio C Hamano +Date: Tue, 17 Jan 2011 13:00:00 -0800 +Subject: Using signed tag in pull requests +Abstract: Beginning v1.7.9, a contributor can push a signed tag to her + publishing repository and ask her integrator to pull it. This assures the + integrator that the pulled history is authentic and allows others to + later validate it. +Content-type: text/asciidoc + +Using signed tag in pull requests +================================= + +A typical distributed workflow using Git is for a contributor to fork a +project, build on it, publish the result to her public repository, and ask +the "upstream" person (often the owner of the project where she forked +from) to pull from her public repository. Requesting such a "pull" is made +easy by the `git request-pull` command. + +Earlier, a typical pull request may have started like this: + +------------ + The following changes since commit 406da78032179...: + + Froboz 3.2 (2011-09-30 14:20:57 -0700) + + are available in the git repository at: + + example.com:/git/froboz.git for-xyzzy +------------ + +followed by a shortlog of the changes and a diffstat. + +The request was for a branch name (e.g. `for-xyzzy`) in the public +repository of the contributor, and even though it stated where the +contributor forked her work from, the message did not say anything about +the commit to expect at the tip of the for-xyzzy branch. If the site that +hosts the public repository of the contributor cannot be fully trusted, it +was unnecessarily hard to make sure what was pulled by the integrator was +genuinely what the contributor had produced for the project. Also there +was no easy way for third-party auditors to later verify the resulting +history. + +Starting from Git release v1.7.9, a contributor can add a signed tag to +the commit at the tip of the history and ask the integrator to pull that +signed tag. When the integrator runs `git pull`, the signed tag is +automatically verified to assure that the history is not tampered with. +In addition, the resulting merge commit records the content of the signed +tag, so that other people can verify that the branch merged by the +integrator was signed by the contributor, without fetching the signed tag +used to validate the pull request separately and keeping it in the refs +namespace. + +This document describes the workflow between the contributor and the +integrator, using Git v1.7.9 or later. + + +A contributor or a lieutenant +----------------------------- + +After preparing her work to be pulled, the contributor uses `git tag -s` +to create a signed tag: + +------------ + $ git checkout work + $ ... "git pull" from sublieutenants, "git commit" your own work ... + $ git tag -s -m "Completed frotz feature" frotz-for-xyzzy work +------------ + +Note that this example uses the `-m` option to create a signed tag with +just a one-liner message, but this is for illustration purposes only. It +is advisable to compose a well-written explanation of what the topic does +to justify why it is worthwhile for the integrator to pull it, as this +message will eventually become part of the final history after the +integrator responds to the pull request (as we will see later). + +Then she pushes the tag out to her public repository: + +------------ + $ git push example.com:/git/froboz.git/ +frotz-for-xyzzy +------------ + +There is no need to push the `work` branch or anything else. + +Note that the above command line used a plus sign at the beginning of +`+frotz-for-xyzzy` to allow forcing the update of a tag, as the same +contributor may want to reuse a signed tag with the same name after the +previous pull request has already been responded to. + +The contributor then prepares a message to request a "pull": + +------------ + $ git request-pull v3.2 example.com:/git/froboz.git/ frotz-for-xyzzy >msg.txt +------------ + +The arguments are: + +. the version of the integrator's commit the contributor based her work on; +. the URL of the repository, to which the contributor has pushed what she + wants to get pulled; and +. the name of the tag the contributor wants to get pulled (earlier, she could + write only a branch name here). + +The resulting msg.txt file begins like so: + +------------ + The following changes since commit 406da78032179...: + + Froboz 3.2 (2011-09-30 14:20:57 -0700) + + are available in the git repository at: + + example.com:/git/froboz.git tags/frotz-for-xyzzy + + for you to fetch changes up to 703f05ad5835c...: + + Add tests and documentation for frotz (2011-12-02 10:02:52 -0800) + + ----------------------------------------------- + Completed frotz feature + ----------------------------------------------- +------------ + +followed by a shortlog of the changes and a diffstat. Comparing this with +the earlier illustration of the output from the traditional `git request-pull` +command, the reader should notice that: + +. The tip commit to expect is shown to the integrator; and +. The signed tag message is shown prominently between the dashed lines + before the shortlog. + +The latter is why the contributor would want to justify why pulling her +work is worthwhile when creating the signed tag. The contributor then +opens her favorite MUA, reads msg.txt, edits and sends it to her upstream +integrator. + + +Integrator +---------- + +After receiving such a pull request message, the integrator fetches and +integrates the tag named in the request, with: + +------------ + $ git pull example.com:/git/froboz.git/ tags/frotz-for-xyzzy +------------ + +This operation will always open an editor to allow the integrator to fine +tune the commit log message when merging a signed tag. Also, pulling a +signed tag will always create a merge commit even when the integrator does +not have any new commit since the contributor's work forked (i.e. 'fast +forward'), so that the integrator can properly explain what the merge is +about and why it was made. + +In the editor, the integrator will see something like this: + +------------ + Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/ + + Completed frotz feature + # gpg: Signature made Fri 02 Dec 2011 10:03:01 AM PST using RSA key ID 96AFE6CB + # gpg: Good signature from "Con Tributor " +------------ + +Notice that the message recorded in the signed tag "Completed frotz +feature" appears here, and again that is why it is important for the +contributor to explain her work well when creating the signed tag. + +As usual, the lines commented with `#` are stripped out. The resulting +commit records the signed tag used for this validation in a hidden field +so that it can later be used by others to audit the history. There is no +need for the integrator to keep a separate copy of the tag in his +repository (i.e. `git tag -l` won't list the `frotz-for-xyzzy` tag in the +above example), and there is no need to publish the tag to his public +repository, either. + +After the integrator responds to the pull request and her work becomes +part of the permanent history, the contributor can remove the tag from +her public repository, if she chooses, in order to keep the tag namespace +of her public repository clean, with: + +------------ + $ git push example.com:/git/froboz.git :frotz-for-xyzzy +------------ + + +Auditors +-------- + +The `--show-signature` option can be given to `git log` or `git show` and +shows the verification status of the embedded signed tag in merge commits +created when the integrator responded to a pull request of a signed tag. + +A typical output from `git show --show-signature` may look like this: + +------------ + $ git show --show-signature + commit 02306ef6a3498a39118aef9df7975bdb50091585 + merged tag 'frotz-for-xyzzy' + gpg: Signature made Fri 06 Jan 2012 12:41:49 PM PST using RSA key ID 96AFE6CB + gpg: Good signature from "Con Tributor " + Merge: 406da78 703f05a + Author: Inte Grator + Date: Tue Jan 17 13:49:41 2012 -0800 + + Merge tag 'frotz-for-xyzzy' of example.com:/git/froboz.git/ + + Completed frotz feature + + * tag 'frotz-for-xyzzy' (100 commits) + Add tests and documentation for frotz + ... +------------ + +There is no need for the auditor to explicitly fetch the contributor's +signature, or to even be aware of what tag(s) the contributor and integrator +used to communicate the signature. All the required information is recorded +as part of the merge commit. diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 1a5c12e31..5afc99f65 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -8,18 +8,33 @@ failed and do not autocommit, to give the user a chance to inspect and further tweak the merge result before committing. --edit:: --e:: +--no-edit:: Invoke editor before committing successful merge to further - edit the default merge message. + edit the default merge message. The `--no-edit` option can be + used to accept the auto-generated message (this is generally + discouraged) when merging an annotated tag, in which case + `git merge` automatically spawns the editor so that the result + of the GPG verification of the tag can be seen. ++ +Older scripts may depend on the historical behaviour of not allowing the +user to edit the merge log message. They will see an editor opened when +they run `git merge` to merge an annotated tag. To make it easier to adjust +such scripts to the updated behaviour, the environment variable +`GIT_MERGE_AUTOEDIT` can be set to `no` at the beginning of them. --ff:: + When the merge resolves as a fast-forward, only update the branch + pointer, without creating a merge commit. This is the default + behavior. + --no-ff:: - Do not generate a merge commit if the merge resolved as - a fast-forward, only update the branch pointer. This is - the default behavior of git-merge. -+ -With --no-ff Generate a merge commit even if the merge -resolved as a fast-forward. + Create a merge commit even when the merge resolves as a + fast-forward. + +--ff-only:: + Refuse to merge and exit with a non-zero status unless the + current `HEAD` is already up-to-date or the merge can be + resolved as a fast-forward. --log[=]:: --no-log:: @@ -54,11 +69,6 @@ merge. With --no-squash perform the merge and commit the result. This option can be used to override --squash. ---ff-only:: - Refuse to merge and exit with a non-zero status unless the - current `HEAD` is already up-to-date or the merge can be - resolved as a fast-forward. - -s :: --strategy=:: Use the given merge strategy; can be supplied more than diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 561cc9f7d..880b6f2e6 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -132,6 +132,10 @@ The placeholders are: - '%N': commit notes - '%gD': reflog selector, e.g., `refs/stash@\{1\}` - '%gd': shortened reflog selector, e.g., `stash@\{1\}` +- '%gn': reflog identity name +- '%gN': reflog identity name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) +- '%ge': reflog identity email +- '%gE': reflog identity email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) - '%gs': reflog subject - '%Cred': switch color to red - '%Cgreen': switch color to green diff --git a/Documentation/revisions.txt b/Documentation/revisions.txt index b290b617d..172566183 100644 --- a/Documentation/revisions.txt +++ b/Documentation/revisions.txt @@ -101,7 +101,7 @@ the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file. '{tilde}', e.g. 'master{tilde}3':: A suffix '{tilde}' to a revision parameter means the commit - object that is the th generation grand-parent of the named + object that is the th generation ancestor of the named commit object, following only the first parents. I.e. '{tilde}3' is equivalent to '{caret}{caret}{caret}' which is equivalent to '{caret}1{caret}1{caret}1'. See below for an illustration of diff --git a/Documentation/technical/api-credentials.txt b/Documentation/technical/api-credentials.txt new file mode 100644 index 000000000..21ca6a255 --- /dev/null +++ b/Documentation/technical/api-credentials.txt @@ -0,0 +1,245 @@ +credentials API +=============== + +The credentials API provides an abstracted way of gathering username and +password credentials from the user (even though credentials in the wider +world can take many forms, in this document the word "credential" always +refers to a username and password pair). + +Data Structures +--------------- + +`struct credential`:: + + This struct represents a single username/password combination + along with any associated context. All string fields should be + heap-allocated (or NULL if they are not known or not applicable). + The meaning of the individual context fields is the same as + their counterparts in the helper protocol; see the section below + for a description of each field. ++ +The `helpers` member of the struct is a `string_list` of helpers. Each +string specifies an external helper which will be run, in order, to +either acquire or store credentials. See the section on credential +helpers below. ++ +This struct should always be initialized with `CREDENTIAL_INIT` or +`credential_init`. + + +Functions +--------- + +`credential_init`:: + + Initialize a credential structure, setting all fields to empty. + +`credential_clear`:: + + Free any resources associated with the credential structure, + returning it to a pristine initialized state. + +`credential_fill`:: + + Instruct the credential subsystem to fill the username and + password fields of the passed credential struct by first + consulting helpers, then asking the user. After this function + returns, the username and password fields of the credential are + guaranteed to be non-NULL. If an error occurs, the function will + die(). + +`credential_reject`:: + + Inform the credential subsystem that the provided credentials + have been rejected. This will cause the credential subsystem to + notify any helpers of the rejection (which allows them, for + example, to purge the invalid credentials from storage). It + will also free() the username and password fields of the + credential and set them to NULL (readying the credential for + another call to `credential_fill`). Any errors from helpers are + ignored. + +`credential_approve`:: + + Inform the credential subsystem that the provided credentials + were successfully used for authentication. This will cause the + credential subsystem to notify any helpers of the approval, so + that they may store the result to be used again. Any errors + from helpers are ignored. + +`credential_from_url`:: + + Parse a URL into broken-down credential fields. + +Example +------- + +The example below shows how the functions of the credential API could be +used to login to a fictitious "foo" service on a remote host: + +----------------------------------------------------------------------- +int foo_login(struct foo_connection *f) +{ + int status; + /* + * Create a credential with some context; we don't yet know the + * username or password. + */ + + struct credential c = CREDENTIAL_INIT; + c.protocol = xstrdup("foo"); + c.host = xstrdup(f->hostname); + + /* + * Fill in the username and password fields by contacting + * helpers and/or asking the user. The function will die if it + * fails. + */ + credential_fill(&c); + + /* + * Otherwise, we have a username and password. Try to use it. + */ + status = send_foo_login(f, c.username, c.password); + switch (status) { + case FOO_OK: + /* It worked. Store the credential for later use. */ + credential_accept(&c); + break; + case FOO_BAD_LOGIN: + /* Erase the credential from storage so we don't try it + * again. */ + credential_reject(&c); + break; + default: + /* + * Some other error occured. We don't know if the + * credential is good or bad, so report nothing to the + * credential subsystem. + */ + } + + /* Free any associated resources. */ + credential_clear(&c); + + return status; +} +----------------------------------------------------------------------- + + +Credential Helpers +------------------ + +Credential helpers are programs executed by git to fetch or save +credentials from and to long-term storage (where "long-term" is simply +longer than a single git process; e.g., credentials may be stored +in-memory for a few minutes, or indefinitely on disk). + +Each helper is specified by a single string. The string is transformed +by git into a command to be executed using these rules: + + 1. If the helper string begins with "!", it is considered a shell + snippet, and everything after the "!" becomes the command. + + 2. Otherwise, if the helper string begins with an absolute path, the + verbatim helper string becomes the command. + + 3. Otherwise, the string "git credential-" is prepended to the helper + string, and the result becomes the command. + +The resulting command then has an "operation" argument appended to it +(see below for details), and the result is executed by the shell. + +Here are some example specifications: + +---------------------------------------------------- +# run "git credential-foo" +foo + +# same as above, but pass an argument to the helper +foo --bar=baz + +# the arguments are parsed by the shell, so use shell +# quoting if necessary +foo --bar="whitespace arg" + +# you can also use an absolute path, which will not use the git wrapper +/path/to/my/helper --with-arguments + +# or you can specify your own shell snippet +!f() { echo "password=`cat $HOME/.secret`"; }; f +---------------------------------------------------- + +Generally speaking, rule (3) above is the simplest for users to specify. +Authors of credential helpers should make an effort to assist their +users by naming their program "git-credential-$NAME", and putting it in +the $PATH or $GIT_EXEC_PATH during installation, which will allow a user +to enable it with `git config credential.helper $NAME`. + +When a helper is executed, it will have one "operation" argument +appended to its command line, which is one of: + +`get`:: + + Return a matching credential, if any exists. + +`store`:: + + Store the credential, if applicable to the helper. + +`erase`:: + + Remove a matching credential, if any, from the helper's storage. + +The details of the credential will be provided on the helper's stdin +stream. The credential is split into a set of named attributes. +Attributes are provided to the helper, one per line. Each attribute is +specified by a key-value pair, separated by an `=` (equals) sign, +followed by a newline. The key may contain any bytes except `=`, +newline, or NUL. The value may contain any bytes except newline or NUL. +In both cases, all bytes are treated as-is (i.e., there is no quoting, +and one cannot transmit a value with newline or NUL in it). The list of +attributes is terminated by a blank line or end-of-file. + +Git will send the following attributes (but may not send all of +them for a given credential; for example, a `host` attribute makes no +sense when dealing with a non-network protocol): + +`protocol`:: + + The protocol over which the credential will be used (e.g., + `https`). + +`host`:: + + The remote hostname for a network credential. + +`path`:: + + The path with which the credential will be used. E.g., for + accessing a remote https repository, this will be the + repository's path on the server. + +`username`:: + + The credential's username, if we already have one (e.g., from a + URL, from the user, or from a previously run helper). + +`password`:: + + The credential's password, if we are asking it to be stored. + +For a `get` operation, the helper should produce a list of attributes +on stdout in the same format. A helper is free to produce a subset, or +even no values at all if it has nothing useful to provide. Any provided +attributes will overwrite those already known about by git. + +For a `store` or `erase` operation, the helper's output is ignored. +If it fails to perform the requested operation, it may complain to +stderr to inform the user. If it does not support the requested +operation (e.g., a read-only store), it should silently ignore the +request. + +If a helper receives any other operation, it should silently ignore the +request. This leaves room for future operations to be added (older +helpers will just ignore the new requests). diff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt index ce24eb96f..5a0c14fce 100644 --- a/Documentation/technical/api-string-list.txt +++ b/Documentation/technical/api-string-list.txt @@ -83,7 +83,9 @@ Functions Insert a new element to the string_list. The returned pointer can be handy if you want to write something to the `util` pointer of the - string_list_item containing the just added string. + string_list_item containing the just added string. If the given + string already exists the insertion will be skipped and the + pointer to the existing item returned. + Since this function uses xrealloc() (which die()s if it fails) if the list needs to grow, it is safe not to check the pointer. I.e. you may diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index c95c200ee..ba8c50302 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.8.5 +DEF_VER=v1.7.9.6 LF=' ' diff --git a/INSTALL b/INSTALL index bf0d97ef7..58b2b86cc 100644 --- a/INSTALL +++ b/INSTALL @@ -28,16 +28,25 @@ set up install paths (via config.mak.autogen), so you can write instead If you're willing to trade off (much) longer build time for a later faster git you can also do a profile feedback build with - $ make profile-all - # make prefix=... install + $ make prefix=/usr PROFILE=BUILD all + # make prefix=/usr PROFILE=BUILD install This will run the complete test suite as training workload and then rebuild git with the generated profile feedback. This results in a git which is a few percent faster on CPU intensive workloads. This may be a good tradeoff for distribution packagers. -Note that the profile feedback build stage currently generates -a lot of additional compiler warnings. +Or if you just want to install a profile-optimized version of git into +your home directory, you could run: + + $ make PROFILE=BUILD install + +As a caveat: a profile-optimized build takes a *lot* longer since the +git tree must be built twice, and in order for the profiling +measurements to work properly, ccache must be disabled and the test +suite has to be run using only a single CPU. In addition, the profile +feedback build stage currently generates a lot of additional compiler +warnings. Issues of note: @@ -83,7 +92,11 @@ Issues of note: - "Perl" version 5.8 or later is needed to use some of the features (e.g. preparing a partial commit using "git add -i/-p", interacting with svn repositories with "git svn"). If you can - live without these, use NO_PERL. + live without these, use NO_PERL. Note that recent releases of + Redhat/Fedora are reported to ship Perl binary package with some + core modules stripped away (see http://lwn.net/Articles/477234/), + so you might need to install additional packages other than Perl + itself, e.g. Time::HiRes. - "openssl" library is used by git-imap-send to use IMAP over SSL. If you don't need it, use NO_OPENSSL. @@ -106,6 +119,18 @@ Issues of note: history graphically, and in git-gui. If you don't want gitk or git-gui, you can use NO_TCLTK. + - A gettext library is used by default for localizing Git. The + primary target is GNU libintl, but the Solaris gettext + implementation also works. + + We need a gettext.h on the system for C code, gettext.sh (or + Solaris gettext(1)) for shell scripts, and libintl-perl for Perl + programs. + + Set NO_GETTEXT to disable localization support and make Git only + use English. Under autoconf the configure script will do this + automatically if it can't find libintl on the system. + - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have diff --git a/Makefile b/Makefile index 6295422e1..e4f8e0ef0 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,26 @@ all:: # Define EXPATDIR=/foo/bar if your expat header and library files are in # /foo/bar/include and /foo/bar/lib directories. # +# Define NO_GETTEXT if you don't want Git output to be translated. +# A translated Git requires GNU libintl or another gettext implementation, +# plus libintl-perl at runtime. +# +# Define HAVE_LIBCHARSET_H if you haven't set NO_GETTEXT and you can't +# trust the langinfo.h's nl_langinfo(CODESET) function to return the +# current character set. GNU and Solaris have a nl_langinfo(CODESET), +# FreeBSD can use either, but MinGW and some others need to use +# libcharset.h's locale_charset() instead. +# +# Define CHARSET_LIB to you need to link with library other than -liconv to +# use locale_charset() function. On some platforms this needs to set to +# -lcharset +# +# Define LIBC_CONTAINS_LIBINTL if your gettext implementation doesn't +# need -lintl when linking. +# +# Define NO_MSGFMT_EXTENDED_OPTIONS if your implementation of msgfmt +# doesn't support GNU extensions like --check and --statistics +# # Define HAVE_PATHS_H if you have paths.h and want to use the default PATH # it specifies. # @@ -143,6 +163,8 @@ all:: # # Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). # +# Define NO_UNIX_SOCKETS if your system does not offer unix sockets. +# # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. # @@ -227,6 +249,9 @@ all:: # # Define NO_REGEX if you have no or inferior regex support in your C library. # +# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the +# user. +# # Define GETTEXT_POISON if you are debugging the choice of strings marked # for translation. In a GETTEXT_POISON build, you can turn all strings marked # for translation into gibberish by setting the GIT_GETTEXT_POISON variable @@ -307,6 +332,7 @@ gitexecdir = libexec/git-core mergetoolsdir = $(gitexecdir)/mergetools sharedir = $(prefix)/share gitwebdir = $(sharedir)/gitweb +localedir = $(sharedir)/locale template_dir = share/git-core/templates htmldir = share/doc/git-doc ETC_GITCONFIG = $(sysconfdir)/gitconfig @@ -315,9 +341,9 @@ lib = lib # DESTDIR= pathsep = : -export prefix bindir sharedir sysconfdir gitwebdir +export prefix bindir sharedir sysconfdir gitwebdir localedir -CC = gcc +CC = cc AR = ar RM = rm -f DIFF = diff @@ -328,6 +354,7 @@ RPMBUILD = rpmbuild TCL_PATH = tclsh TCLTK_PATH = wish XGETTEXT = xgettext +MSGFMT = msgfmt PTHREAD_LIBS = -lpthread PTHREAD_CFLAGS = GCOV = gcov @@ -427,10 +454,15 @@ PROGRAM_OBJS += show-index.o PROGRAM_OBJS += upload-pack.o PROGRAM_OBJS += http-backend.o PROGRAM_OBJS += sh-i18n--envsubst.o +PROGRAM_OBJS += credential-store.o + +# Binary suffix, set to .exe for Windows builds +X = PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS)) TEST_PROGRAMS_NEED_X += test-chmtime +TEST_PROGRAMS_NEED_X += test-credential TEST_PROGRAMS_NEED_X += test-ctype TEST_PROGRAMS_NEED_X += test-date TEST_PROGRAMS_NEED_X += test-delta @@ -512,6 +544,7 @@ LIB_H += argv-array.h LIB_H += attr.h LIB_H += blob.h LIB_H += builtin.h +LIB_H += bulk-checkin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += color.h @@ -520,12 +553,14 @@ LIB_H += compat/bswap.h LIB_H += compat/cygwin.h LIB_H += compat/mingw.h LIB_H += compat/obstack.h +LIB_H += compat/terminal.h LIB_H += compat/win32/pthread.h LIB_H += compat/win32/syslog.h LIB_H += compat/win32/poll.h LIB_H += compat/win32/dirent.h LIB_H += connected.h LIB_H += convert.h +LIB_H += credential.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -533,9 +568,11 @@ LIB_H += diffcore.h LIB_H += diff.h LIB_H += dir.h LIB_H += exec_cmd.h +LIB_H += fmt-merge-msg.h LIB_H += fsck.h LIB_H += gettext.h LIB_H += git-compat-util.h +LIB_H += gpg-interface.h LIB_H += graph.h LIB_H += grep.h LIB_H += hash.h @@ -559,6 +596,7 @@ LIB_H += parse-options.h LIB_H += patch-ids.h LIB_H += pkt-line.h LIB_H += progress.h +LIB_H += prompt.h LIB_H += quote.h LIB_H += reflog-walk.h LIB_H += refs.h @@ -600,17 +638,20 @@ LIB_OBJS += base85.o LIB_OBJS += bisect.o LIB_OBJS += blob.o LIB_OBJS += branch.o +LIB_OBJS += bulk-checkin.o LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o LIB_OBJS += color.o LIB_OBJS += combine-diff.o LIB_OBJS += commit.o LIB_OBJS += compat/obstack.o +LIB_OBJS += compat/terminal.o LIB_OBJS += config.o LIB_OBJS += connect.o LIB_OBJS += connected.o LIB_OBJS += convert.o LIB_OBJS += copy.o +LIB_OBJS += credential.o LIB_OBJS += csum-file.o LIB_OBJS += ctype.o LIB_OBJS += date.o @@ -630,6 +671,8 @@ LIB_OBJS += entry.o LIB_OBJS += environment.o LIB_OBJS += exec_cmd.o LIB_OBJS += fsck.o +LIB_OBJS += gpg-interface.o +LIB_OBJS += gettext.o LIB_OBJS += graph.o LIB_OBJS += grep.o LIB_OBJS += hash.o @@ -665,6 +708,7 @@ LIB_OBJS += pkt-line.o LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += progress.o +LIB_OBJS += prompt.o LIB_OBJS += quote.o LIB_OBJS += reachable.o LIB_OBJS += read-cache.o @@ -826,12 +870,15 @@ ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease + HAVE_DEV_TTY = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease endif ifeq ($(uname_S),UnixWare) CC = cc @@ -886,6 +933,7 @@ ifeq ($(uname_S),Darwin) endif NO_MEMMEM = YesPlease USE_ST_TIMESPEC = YesPlease + HAVE_DEV_TTY = YesPlease endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -898,6 +946,7 @@ ifeq ($(uname_S),SunOS) NO_MKSTEMPS = YesPlease NO_REGEX = YesPlease NO_FNMATCH_CASEFOLD = YesPlease + NO_MSGFMT_EXTENDED_OPTIONS = YesPlease ifeq ($(uname_R),5.6) SOCKLEN_T = int NO_HSTRERROR = YesPlease @@ -1021,6 +1070,7 @@ ifeq ($(uname_S),GNU) NO_STRLCPY=YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + LIBC_CONTAINS_LIBINTL = YesPlease endif ifeq ($(uname_S),IRIX) NO_SETENV = YesPlease @@ -1100,6 +1150,7 @@ ifeq ($(uname_S),Windows) NO_SYS_POLL_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease + NO_UNIX_SOCKETS = YesPlease NO_SETENV = YesPlease NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease @@ -1193,6 +1244,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_LIBGEN_H = YesPlease NO_SYS_POLL_H = YesPlease NO_SYMLINK_HEAD = YesPlease + NO_UNIX_SOCKETS = YesPlease NO_SETENV = YesPlease NO_UNSETENV = YesPlease NO_STRCASESTR = YesPlease @@ -1237,6 +1289,7 @@ ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) EXTLIBS += /mingw/lib/libz.a NO_R_TO_GCC_LINKER = YesPlease INTERNAL_QSORT = YesPlease + HAVE_LIBCHARSET_H = YesPlease else NO_CURL = YesPlease endif @@ -1425,6 +1478,11 @@ endif ifdef NEEDS_LIBGEN EXTLIBS += -lgen endif +ifndef NO_GETTEXT +ifndef LIBC_CONTAINS_LIBINTL + EXTLIBS += -lintl +endif +endif ifdef NEEDS_SOCKET EXTLIBS += -lsocket endif @@ -1467,9 +1525,11 @@ ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif ifdef GETTEXT_POISON - LIB_OBJS += gettext.o BASIC_CFLAGS += -DGETTEXT_POISON endif +ifdef NO_GETTEXT + BASIC_CFLAGS += -DNO_GETTEXT +endif ifdef NO_STRCASESTR COMPAT_CFLAGS += -DNO_STRCASESTR COMPAT_OBJS += compat/strcasestr.o @@ -1570,6 +1630,12 @@ ifdef NO_INET_PTON LIB_OBJS += compat/inet_pton.o BASIC_CFLAGS += -DNO_INET_PTON endif +ifndef NO_UNIX_SOCKETS + LIB_OBJS += unix-socket.o + LIB_H += unix-socket.h + PROGRAM_OBJS += credential-cache.o + PROGRAM_OBJS += credential-cache--daemon.o +endif ifdef NO_ICONV BASIC_CFLAGS += -DNO_ICONV @@ -1632,6 +1698,15 @@ ifdef HAVE_PATHS_H BASIC_CFLAGS += -DHAVE_PATHS_H endif +ifdef HAVE_LIBCHARSET_H + BASIC_CFLAGS += -DHAVE_LIBCHARSET_H + EXTLIBS += $(CHARSET_LIB) +endif + +ifdef HAVE_DEV_TTY + BASIC_CFLAGS += -DHAVE_DEV_TTY +endif + ifdef DIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS endif @@ -1652,6 +1727,10 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT export GIT_TEST_CMP_USE_COPIED_CONTEXT endif +ifndef NO_MSGFMT_EXTENDED_OPTIONS + MSGFMT += --check --statistics +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks endif @@ -1682,6 +1761,7 @@ ifndef V QUIET_GEN = @echo ' ' GEN $@; QUIET_LNCP = @echo ' ' LN/CP $@; QUIET_XGETTEXT = @echo ' ' XGETTEXT $@; + QUIET_MSGFMT = @echo ' ' MSGFMT $@; QUIET_GCOV = @echo ' ' GCOV $@; QUIET_SP = @echo ' ' SP $<; QUIET_SUBDIR0 = +@subdir= @@ -1697,6 +1777,26 @@ ifdef ASCIIDOC7 export ASCIIDOC7 endif +### profile feedback build +# + +# Can adjust this to be a global directory if you want to do extended +# data gathering +PROFILE_DIR := $(CURDIR) + +ifeq ("$(PROFILE)","GEN") + CFLAGS += -fprofile-generate=$(PROFILE_DIR) -DNO_NORETURN=1 + EXTLIBS += -lgcov + export CCACHE_DISABLE=t + V=1 +else +ifneq ("$(PROFILE)","") + CFLAGS += -fprofile-use=$(PROFILE_DIR) -fprofile-correction -DNO_NORETURN=1 + export CCACHE_DISABLE=t + V=1 +endif +endif + # Shell quote (do not use $(call) to accommodate ancient setups); SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) @@ -1708,6 +1808,7 @@ bindir_SQ = $(subst ','\'',$(bindir)) bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) mandir_SQ = $(subst ','\'',$(mandir)) infodir_SQ = $(subst ','\'',$(infodir)) +localedir_SQ = $(subst ','\'',$(localedir)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) template_dir_SQ = $(subst ','\'',$(template_dir)) htmldir_SQ = $(subst ','\'',$(htmldir)) @@ -1752,7 +1853,17 @@ export DIFF TAR INSTALL DESTDIR SHELL_PATH SHELL = $(SHELL_PATH) -all:: shell_compatibility_test $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS +all:: shell_compatibility_test + +ifeq "$(PROFILE)" "BUILD" +ifeq ($(filter all,$(MAKECMDGOALS)),all) +all:: profile-clean + $(MAKE) PROFILE=GEN all + $(MAKE) PROFILE=GEN -j1 test +endif +endif + +all:: $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test -d '$p' -o '$p' -ef '$p$X' || $(RM) '$p';) endif @@ -1763,7 +1874,7 @@ ifndef NO_TCLTK $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all endif ifndef NO_PERL - $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all + $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all endif ifndef NO_PYTHON $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all @@ -1813,6 +1924,7 @@ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ -e 's|@@DIFF@@|$(DIFF_SQ)|' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + -e 's|@@LOCALEDIR@@|$(localedir_SQ)|g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e $(BROKEN_PATH_FIX) \ $@.sh >$@+ @@ -2065,6 +2177,9 @@ config.sp config.s config.o: EXTRA_CPPFLAGS = \ attr.sp attr.s attr.o: EXTRA_CPPFLAGS = \ -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"' +gettext.sp gettext.s gettext.o: EXTRA_CPPFLAGS = \ + -DGIT_LOCALE_PATH='"$(localedir_SQ)"' + http.sp http.s http.o: EXTRA_CPPFLAGS = \ -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"' @@ -2138,17 +2253,37 @@ XGETTEXT_FLAGS = \ XGETTEXT_FLAGS_C = $(XGETTEXT_FLAGS) --language=C \ --keyword=_ --keyword=N_ --keyword="Q_:1,2" XGETTEXT_FLAGS_SH = $(XGETTEXT_FLAGS) --language=Shell +XGETTEXT_FLAGS_PERL = $(XGETTEXT_FLAGS) --keyword=__ --language=Perl LOCALIZED_C := $(C_OBJ:o=c) LOCALIZED_SH := $(SCRIPT_SH) +LOCALIZED_PERL := $(SCRIPT_PERL) + +ifdef XGETTEXT_INCLUDE_TESTS +LOCALIZED_C += t/t0200/test.c +LOCALIZED_SH += t/t0200/test.sh +LOCALIZED_PERL += t/t0200/test.perl +endif po/git.pot: $(LOCALIZED_C) $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ $(XGETTEXT_FLAGS_C) $(LOCALIZED_C) $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_SH) \ $(LOCALIZED_SH) + $(QUIET_XGETTEXT)$(XGETTEXT) -o$@+ --join-existing $(XGETTEXT_FLAGS_PERL) \ + $(LOCALIZED_PERL) mv $@+ $@ pot: po/git.pot +POFILES := $(wildcard po/*.po) +MOFILES := $(patsubst po/%.po,po/build/locale/%/LC_MESSAGES/git.mo,$(POFILES)) + +ifndef NO_GETTEXT +all:: $(MOFILES) +endif + +po/build/locale/%/LC_MESSAGES/git.mo: po/%.po + $(QUIET_MSGFMT)mkdir -p $(dir $@) && $(MSGFMT) -o $@ $< + FIND_SOURCE_FILES = ( git ls-files '*.[hcS]' 2>/dev/null || \ $(FIND) . \( -name .git -type d -prune \) \ -o \( -name '*.[hcS]' -type f -print \) ) @@ -2167,7 +2302,8 @@ cscope: ### Detect prefix changes TRACK_CFLAGS = $(CC):$(subst ','\'',$(ALL_CFLAGS)):\ - $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ) + $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):\ + $(localedir_SQ) GIT-CFLAGS: FORCE @FLAGS='$(TRACK_CFLAGS)'; \ @@ -2204,7 +2340,9 @@ endif ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT @echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@ endif + @echo NO_GETTEXT=\''$(subst ','\'',$(subst ','\'',$(NO_GETTEXT)))'\' >>$@ @echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@ + @echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -2319,6 +2457,11 @@ install: all $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' $(INSTALL) -m 644 mergetools/* '$(DESTDIR_SQ)$(mergetools_instdir_SQ)' +ifndef NO_GETTEXT + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(localedir_SQ)' + (cd po/build/locale && $(TAR) cf - .) | \ + (cd '$(DESTDIR_SQ)$(localedir_SQ)' && umask 022 && $(TAR) xof -) +endif ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C gitweb install @@ -2448,13 +2591,18 @@ distclean: clean $(RM) configure $(RM) po/git.pot -clean: +profile-clean: + $(RM) $(addsuffix *.gcda,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) + $(RM) $(addsuffix *.gcno,$(addprefix $(PROFILE_DIR)/, $(object_dirs))) + +clean: profile-clean $(RM) *.o block-sha1/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o vcs-svn/*.o \ builtin/*.o $(LIB_FILE) $(XDIFF_LIB) $(VCSSVN_LIB) $(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) $(RM) -r bin-wrappers $(RM) -r $(dep_dirs) + $(RM) -r po/build/ $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) tags cscope* $(RM) -r autom4te.cache $(RM) config.log config.mak.autogen config.mak.append config.status config.cache @@ -2477,7 +2625,7 @@ ifndef NO_TCLTK endif $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS -.PHONY: all install clean strip +.PHONY: all install profile-clean clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell .PHONY: FORCE cscope @@ -2587,18 +2735,3 @@ cover_db: coverage-report cover_db_html: cover_db cover -report html -outputdir cover_db_html cover_db -### profile feedback build -# -.PHONY: profile-all profile-clean - -PROFILE_GEN_CFLAGS := $(CFLAGS) -fprofile-generate -DNO_NORETURN=1 -PROFILE_USE_CFLAGS := $(CFLAGS) -fprofile-use -fprofile-correction -DNO_NORETURN=1 - -profile-clean: - $(RM) $(addsuffix *.gcda,$(object_dirs)) - $(RM) $(addsuffix *.gcno,$(object_dirs)) - -profile-all: profile-clean - $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" all - $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" -j1 test - $(MAKE) CFLAGS="$(PROFILE_USE_CFLAGS)" all diff --git a/README b/README index 67cfeb201..d2690ec8d 100644 --- a/README +++ b/README @@ -42,10 +42,12 @@ including full documentation and Git related tools. The user discussion and development of Git take place on the Git mailing list -- everyone is welcome to post bug reports, feature -requests, comments and patches to git@vger.kernel.org. To subscribe -to the list, send an email with just "subscribe git" in the body to -majordomo@vger.kernel.org. The mailing list archives are available at -http://marc.theaimsgroup.com/?l=git and other archival sites. +requests, comments and patches to git@vger.kernel.org (read +Documentation/SubmittingPatches for instructions on patch submission). +To subscribe to the list, send an email with just "subscribe git" in +the body to majordomo@vger.kernel.org. The mailing list archives are +available at http://marc.theaimsgroup.com/?l=git and other archival +sites. The messages titled "A note from the maintainer", "What's in git.git (stable)" and "What's cooking in git.git (topics)" and diff --git a/RelNotes b/RelNotes index dfc0e738d..3f4ce9f4f 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/1.7.8.5.txt \ No newline at end of file +Documentation/RelNotes/1.7.9.6.txt \ No newline at end of file diff --git a/archive.c b/archive.c index 164bbd014..1ee837d71 100644 --- a/archive.c +++ b/archive.c @@ -260,14 +260,23 @@ static void parse_treeish_arg(const char **argv, /* Remotes are only allowed to fetch actual refs */ if (remote) { char *ref = NULL; - if (!dwim_ref(name, strlen(name), sha1, &ref)) - die("no such ref: %s", name); + const char *refname, *colon = NULL; + + colon = strchr(name, ':'); + if (colon) + refname = xstrndup(name, colon - name); + else + refname = name; + + if (!dwim_ref(refname, strlen(refname), sha1, &ref)) + die("no such ref: %s", refname); + if (refname != name) + free((void *)refname); free(ref); } - else { - if (get_sha1(name, sha1)) - die("Not a valid object name"); - } + + if (get_sha1(name, sha1)) + die("Not a valid object name"); commit = lookup_commit_reference_gently(sha1, 1); if (commit) { diff --git a/bisect.h b/bisect.h index 22f2e4db2..ec3c3ff00 100644 --- a/bisect.h +++ b/bisect.h @@ -15,13 +15,12 @@ extern void print_commit_list(struct commit_list *list, const char *format_cur, const char *format_last); -/* bisect_show_flags flags in struct rev_list_info */ #define BISECT_SHOW_ALL (1<<0) -#define BISECT_SHOW_TRIED (1<<1) +#define REV_LIST_QUIET (1<<1) struct rev_list_info { struct rev_info *revs; - int bisect_show_flags; + int flags; int show_timestamp; int hdr_termination; const char *header_prefix; diff --git a/branch.c b/branch.c index d7fd267b7..9971820a1 100644 --- a/branch.c +++ b/branch.c @@ -135,6 +135,37 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, return 0; } +struct branch_desc_cb { + const char *config_name; + const char *value; +}; + +static int read_branch_desc_cb(const char *var, const char *value, void *cb) +{ + struct branch_desc_cb *desc = cb; + if (strcmp(desc->config_name, var)) + return 0; + free((char *)desc->value); + return git_config_string(&desc->value, var, value); +} + +int read_branch_desc(struct strbuf *buf, const char *branch_name) +{ + struct branch_desc_cb cb; + struct strbuf name = STRBUF_INIT; + strbuf_addf(&name, "branch.%s.description", branch_name); + cb.config_name = name.buf; + cb.value = NULL; + if (git_config(read_branch_desc_cb, &cb) < 0) { + strbuf_release(&name); + return -1; + } + if (cb.value) + strbuf_addstr(buf, cb.value); + strbuf_release(&name); + return 0; +} + int validate_new_branchname(const char *name, struct strbuf *ref, int force, int attr_only) { @@ -150,7 +181,7 @@ int validate_new_branchname(const char *name, struct strbuf *ref, const char *head; unsigned char sha1[20]; - head = resolve_ref("HEAD", sha1, 0, NULL); + head = resolve_ref_unsafe("HEAD", sha1, 0, NULL); if (!is_bare_repository() && head && !strcmp(head, ref->buf)) die("Cannot force update the current branch."); } diff --git a/branch.h b/branch.h index e125ff4ca..b99c5a369 100644 --- a/branch.h +++ b/branch.h @@ -47,4 +47,9 @@ void remove_branch_state(void); #define BRANCH_CONFIG_VERBOSE 01 extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote); +/* + * Read branch description + */ +extern int read_branch_desc(struct strbuf *, const char *branch_name); + #endif diff --git a/builtin.h b/builtin.h index 0e9da9083..857b9c8aa 100644 --- a/builtin.h +++ b/builtin.h @@ -14,8 +14,14 @@ extern const char git_usage_string[]; extern const char git_more_info_string[]; extern void prune_packed_objects(int); + +struct fmt_merge_msg_opts { + unsigned add_title:1; + int shortlog_len; +}; + extern int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - int merge_title, int shortlog_len); + struct fmt_merge_msg_opts *); extern void commit_notes(struct notes_tree *t, const char *msg); struct notes_rewrite_cfg { @@ -133,6 +139,7 @@ extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_update_server_info(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); +extern int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_var(int argc, const char **argv, const char *prefix); extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index c59b0c98f..b79336d71 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -13,6 +13,7 @@ #include "diff.h" #include "diffcore.h" #include "revision.h" +#include "bulk-checkin.h" static const char * const builtin_add_usage[] = { "git add [options] [--] ...", @@ -279,6 +280,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) argc = setup_revisions(argc, argv, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_PATCH; + DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES); out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) die (_("Could not open '%s' for writing."), file); @@ -458,11 +460,15 @@ int cmd_add(int argc, const char **argv, const char *prefix) free(seen); } + plug_bulk_checkin(); + exit_status |= add_files_to_cache(prefix, pathspec, flags); if (add_new_files) exit_status |= add_files(&dir, flags); + unplug_bulk_checkin(); + finish: if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin/apply.c b/builtin/apply.c index c24dc546d..389898f13 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -14,6 +14,7 @@ #include "builtin.h" #include "string-list.h" #include "dir.h" +#include "diff.h" #include "parse-options.h" /* @@ -3241,7 +3242,7 @@ static void stat_patch_list(struct patch *patch) show_stats(patch); } - printf(" %d files changed, %d insertions(+), %d deletions(-)\n", files, adds, dels); + print_stat_summary(stdout, files, adds, dels); } static void numstat_patch_list(struct patch *patch) diff --git a/builtin/branch.c b/builtin/branch.c index df908ed8f..d8cccf725 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -104,6 +104,7 @@ static int branch_merged(int kind, const char *name, */ struct commit *reference_rev = NULL; const char *reference_name = NULL; + void *reference_name_to_free = NULL; int merged; if (kind == REF_LOCAL_BRANCH) { @@ -114,8 +115,8 @@ static int branch_merged(int kind, const char *name, branch->merge && branch->merge[0] && branch->merge[0]->dst && - (reference_name = - resolve_ref(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) + (reference_name = reference_name_to_free = + resolve_refdup(branch->merge[0]->dst, sha1, 1, NULL)) != NULL) reference_rev = lookup_commit_reference(sha1); } if (!reference_rev) @@ -141,6 +142,7 @@ static int branch_merged(int kind, const char *name, " '%s', even though it is merged to HEAD."), name, reference_name); } + free(reference_name_to_free); return merged; } @@ -186,7 +188,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) free(name); name = xstrdup(mkpath(fmt, bname.buf)); - if (!resolve_ref(name, sha1, 1, NULL)) { + if (read_ref(name, sha1)) { error(_("%sbranch '%s' not found."), remote, bname.buf); ret = 1; @@ -250,7 +252,7 @@ static char *resolve_symref(const char *src, const char *prefix) int flag; const char *dst, *cp; - dst = resolve_ref(src, sha1, 0, &flag); + dst = resolve_ref_unsafe(src, sha1, 0, &flag); if (!(dst && (flag & REF_ISSYMREF))) return NULL; if (prefix && (cp = skip_prefix(dst, prefix))) @@ -528,6 +530,10 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru if (merge_filter != NO_FILTER) { struct commit *filter; filter = lookup_commit_reference_gently(merge_filter_ref, 0); + if (!filter) + die("object '%s' does not point to a commit", + sha1_to_hex(merge_filter_ref)); + filter->object.flags |= UNINTERESTING; add_pending_object(&ref_list.revs, (struct object *) filter, ""); @@ -565,7 +571,6 @@ static int print_ref_list(int kinds, int detached, int verbose, int abbrev, stru static void rename_branch(const char *oldname, const char *newname, int force) { struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; - unsigned char sha1[20]; struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; int recovery = 0; int clobber_head_ok; @@ -578,7 +583,7 @@ static void rename_branch(const char *oldname, const char *newname, int force) * Bad name --- this could be an attempt to rename a * ref that we used to allow to be created by accident. */ - if (resolve_ref(oldref.buf, sha1, 1, NULL)) + if (ref_exists(oldref.buf)) recovery = 1; else die(_("Invalid branch name: '%s'"), oldname); @@ -630,11 +635,49 @@ static int opt_parse_merge_filter(const struct option *opt, const char *arg, int return 0; } +static const char edit_description[] = "BRANCH_DESCRIPTION"; + +static int edit_branch_description(const char *branch_name) +{ + FILE *fp; + int status; + struct strbuf buf = STRBUF_INIT; + struct strbuf name = STRBUF_INIT; + + read_branch_desc(&buf, branch_name); + if (!buf.len || buf.buf[buf.len-1] != '\n') + strbuf_addch(&buf, '\n'); + strbuf_addf(&buf, + "# Please edit the description for the branch\n" + "# %s\n" + "# Lines starting with '#' will be stripped.\n", + branch_name); + fp = fopen(git_path(edit_description), "w"); + if ((fwrite(buf.buf, 1, buf.len, fp) < buf.len) || fclose(fp)) { + strbuf_release(&buf); + return error(_("could not write branch description template: %s\n"), + strerror(errno)); + } + strbuf_reset(&buf); + if (launch_editor(git_path(edit_description), &buf, NULL)) { + strbuf_release(&buf); + return -1; + } + stripspace(&buf, 1); + + strbuf_addf(&name, "branch.%s.description", branch_name); + status = git_config_set(name.buf, buf.buf); + strbuf_release(&name); + strbuf_release(&buf); + + return status; +} + int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, rename = 0, force_create = 0, list = 0; int verbose = 0, abbrev = -1, detached = 0; - int reflog = 0; + int reflog = 0, edit_description = 0; enum branch_track track; int kinds = REF_LOCAL_BRANCH; struct commit_list *with_commit = NULL; @@ -673,6 +716,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), OPT_BOOLEAN(0, "list", &list, "list branch names"), OPT_BOOLEAN('l', "create-reflog", &reflog, "create the branch's reflog"), + OPT_BOOLEAN(0, "edit-description", &edit_description, + "edit the description for the branch"), OPT__FORCE(&force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, @@ -696,10 +741,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) track = git_branch_track; - head = resolve_ref("HEAD", head_sha1, 0, NULL); + head = resolve_refdup("HEAD", head_sha1, 0, NULL); if (!head) die(_("Failed to resolve HEAD as a valid ref.")); - head = xstrdup(head); if (!strcmp(head, "HEAD")) { detached = 1; } else { @@ -712,7 +756,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!delete && !rename && argc == 0) + if (!delete && !rename && !edit_description && argc == 0) list = 1; if (!!delete + !!rename + !!force_create + !!list > 1) @@ -726,7 +770,34 @@ int cmd_branch(int argc, const char **argv, const char *prefix) else if (list) return print_ref_list(kinds, detached, verbose, abbrev, with_commit, argv); - else if (rename) { + else if (edit_description) { + const char *branch_name; + struct strbuf branch_ref = STRBUF_INIT; + + if (detached) + die("Cannot give description to detached HEAD"); + if (!argc) + branch_name = head; + else if (argc == 1) + branch_name = argv[0]; + else + usage_with_options(builtin_branch_usage, options); + + strbuf_addf(&branch_ref, "refs/heads/%s", branch_name); + if (!ref_exists(branch_ref.buf)) { + strbuf_release(&branch_ref); + + if (!argc) + return error("No commit on branch '%s' yet.", + branch_name); + else + return error("No such branch '%s'.", branch_name); + } + strbuf_release(&branch_ref); + + if (edit_branch_description(branch_name)) + return 1; + } else if (rename) { if (argc == 1) rename_branch(head, argv[0], rename > 1); else if (argc == 2) diff --git a/builtin/checkout.c b/builtin/checkout.c index e79003f5b..a76aa2a6f 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -34,6 +34,7 @@ struct checkout_opts { int force_detach; int writeout_stage; int writeout_error; + int overwrite_ignore; /* not set by parse_options */ int branch_exists; @@ -300,7 +301,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, commit_locked_index(lock_file)) die(_("unable to write new index file")); - resolve_ref("HEAD", rev, 0, &flag); + read_ref_full("HEAD", rev, 0, &flag); head = lookup_commit_reference_gently(rev, 1); errs |= post_checkout_hook(head, head, 0); @@ -421,9 +422,11 @@ static int merge_working_tree(struct checkout_opts *opts, topts.gently = opts->merge && old->commit; topts.verbose_update = !opts->quiet; topts.fn = twoway_merge; - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(topts.dir); + if (opts->overwrite_ignore) { + topts.dir = xcalloc(1, sizeof(*topts.dir)); + topts.dir->flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(topts.dir); + } tree = parse_tree_indirect(old->commit ? old->commit->object.sha1 : EMPTY_TREE_SHA1_BIN); @@ -714,15 +717,14 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) { int ret = 0; struct branch_info old; + void *path_to_free; unsigned char rev[20]; int flag; memset(&old, 0, sizeof(old)); - old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag)); + old.path = path_to_free = resolve_refdup("HEAD", rev, 0, &flag); old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) { - free((char *)old.path); + if (!(flag & REF_ISSYMREF)) old.path = NULL; - } if (old.path && !prefixcmp(old.path, "refs/heads/")) old.name = old.path + strlen("refs/heads/"); @@ -736,8 +738,10 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) } ret = merge_working_tree(opts, &old, new); - if (ret) + if (ret) { + free(path_to_free); return ret; + } if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) orphaned_commit_warning(old.commit); @@ -745,7 +749,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) update_refs_for_switch(opts, &old, new); ret = post_checkout_hook(old.commit, new->commit, 1); - free((char *)old.path); + free(path_to_free); return ret || opts->writeout_error; } @@ -884,7 +888,7 @@ static int parse_branchname_arg(int argc, const char **argv, setup_branch_path(new); if (!check_refname_format(new->path, 0) && - resolve_ref(new->path, branch_rev, 1, NULL)) + !read_ref(new->path, branch_rev)) hashcpy(rev, branch_rev); else new->path = NULL; /* not an existing branch */ @@ -918,6 +922,17 @@ static int parse_branchname_arg(int argc, const char **argv, return argcount; } +static int switch_unborn_to_new_branch(struct checkout_opts *opts) +{ + int status; + struct strbuf branch_ref = STRBUF_INIT; + + strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch); + status = create_symref("HEAD", branch_ref.buf, "checkout -b"); + strbuf_release(&branch_ref); + return status; +} + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; @@ -944,6 +959,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 3), OPT__FORCE(&opts.force, "force checkout (throw away local modifications)"), OPT_BOOLEAN('m', "merge", &opts.merge, "perform a 3-way merge with the new branch"), + OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, "update ignored files (default)"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), @@ -955,6 +971,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) memset(&opts, 0, sizeof(opts)); memset(&new, 0, sizeof(new)); + opts.overwrite_ignore = 1; gitmodules_config(); git_config(git_checkout_config, &opts); @@ -1087,5 +1104,13 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (opts.writeout_stage) die(_("--ours/--theirs is incompatible with switching branches.")); + if (!new.commit) { + unsigned char rev[20]; + int flag; + + if (!read_ref_full("HEAD", rev, 0, &flag) && + (flag & REF_ISSYMREF) && is_null_sha1(rev)) + return switch_unborn_to_new_branch(&opts); + } return switch_branches(&opts, &new); } diff --git a/builtin/clone.c b/builtin/clone.c index 86db95473..0fb5956b4 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -45,7 +45,7 @@ static char *option_branch = NULL; static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; -static int option_progress; +static int option_progress = -1; static struct string_list option_config; static struct string_list option_reference; @@ -60,8 +60,8 @@ static int opt_parse_reference(const struct option *opt, const char *arg, int un static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), - OPT_BOOLEAN(0, "progress", &option_progress, - "force progress reporting"), + OPT_BOOL(0, "progress", &option_progress, + "force progress reporting"), OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, "don't create a checkout"), OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), @@ -105,7 +105,7 @@ static const char *argv_submodule[] = { static char *get_repo_path(const char *repo, int *is_bundle) { - static char *suffix[] = { "/.git", ".git", "" }; + static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; static char *bundle_suffix[] = { ".bundle", "" }; struct stat st; int i; @@ -115,7 +115,7 @@ static char *get_repo_path(const char *repo, int *is_bundle) path = mkpath("%s%s", repo, suffix[i]); if (stat(path, &st)) continue; - if (S_ISDIR(st.st_mode)) { + if (S_ISDIR(st.st_mode) && is_git_directory(path)) { *is_bundle = 0; return xstrdup(absolute_path(path)); } else if (S_ISREG(st.st_mode) && st.st_size > 8) { diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index d083795e2..164b655df 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c @@ -8,8 +8,9 @@ #include "tree.h" #include "builtin.h" #include "utf8.h" +#include "gpg-interface.h" -static const char commit_tree_usage[] = "git commit-tree [(-p )...] < changelog"; +static const char commit_tree_usage[] = "git commit-tree [(-p )...] [-S] [-m ] [-F ] ...", @@ -81,10 +82,13 @@ static const char *template_file; static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; static char *fixup_message, *squash_message; -static int all, edit_flag, also, interactive, patch_interactive, only, amend, signoff; +static int all, also, interactive, patch_interactive, only, amend, signoff; +static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int no_post_rewrite, allow_empty_message; static char *untracked_files_arg, *force_date, *ignore_submodule_arg; +static char *sign_commit; + /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -141,9 +145,11 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C/-c/--amend)"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), OPT_FILENAME('t', "template", &template_file, "use specified template file"), - OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), + OPT_BOOL('e', "edit", &edit_flag, "force edit of commit"), OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, /* end commit message options */ OPT_GROUP("Commit contents options"), @@ -190,16 +196,16 @@ static void determine_whence(struct wt_status *s) static const char *whence_s(void) { - char *s = ""; + const char *s = ""; switch (whence) { case FROM_COMMIT: break; case FROM_MERGE: - s = "merge"; + s = _("merge"); break; case FROM_CHERRY_PICK: - s = "cherry-pick"; + s = _("cherry-pick"); break; } @@ -537,6 +543,7 @@ static void determine_author_info(struct strbuf *author_ident) if (author_message) { const char *a, *lb, *rb, *eol; + size_t len; a = strstr(author_message_buffer, "\nauthor "); if (!a) @@ -557,6 +564,11 @@ static void determine_author_info(struct strbuf *author_ident) (a + strlen("\nauthor ")))); email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<"))); date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> "))); + len = eol - (rb + strlen("> ")); + date = xmalloc(len + 2); + *date = '@'; + memcpy(date + 1, rb + strlen("> "), len); + date[len + 1] = '\0'; } if (force_author) { @@ -1019,8 +1031,8 @@ static int parse_and_validate_options(int argc, const char *argv[], if (logfile || message.len || use_message || fixup_message) use_editor = 0; - if (edit_flag) - use_editor = 1; + if (0 <= edit_flag) + use_editor = edit_flag; if (!use_editor) setenv("GIT_EDITOR", ":", 1); @@ -1258,7 +1270,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, struct commit *commit; struct strbuf format = STRBUF_INIT; unsigned char junk_sha1[20]; - const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); + const char *head; struct pretty_print_context pctx = {0}; struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; @@ -1303,6 +1315,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); + head = resolve_ref_unsafe("HEAD", junk_sha1, 0, NULL); printf("[%s%s ", !prefixcmp(head, "refs/heads/") ? head + 11 : @@ -1323,6 +1336,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1, static int git_commit_config(const char *k, const char *v, void *cb) { struct wt_status *s = cb; + int status; if (!strcmp(k, "commit.template")) return git_config_pathname(&template_file, k, v); @@ -1331,6 +1345,9 @@ static int git_commit_config(const char *k, const char *v, void *cb) return 0; } + status = git_gpg_config(k, v, NULL); + if (status) + return status; return git_status_config(k, v, s); } @@ -1381,6 +1398,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) int allow_fast_forward = 1; struct wt_status s; struct commit *current_head = NULL; + struct commit_extra_header *extra = NULL; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1424,7 +1442,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) pptr = &commit_list_insert(c->item, pptr)->next; } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; - struct commit *commit; FILE *fp; if (!reflog_msg) @@ -1435,11 +1452,12 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die_errno(_("could not open '%s' for reading"), git_path("MERGE_HEAD")); while (strbuf_getline(&m, fp, '\n') != EOF) { - unsigned char sha1[20]; - if (get_sha1_hex(m.buf, sha1) < 0) + struct commit *parent; + + parent = get_merge_parent(m.buf); + if (!parent) die(_("Corrupt MERGE_HEAD file (%s)"), m.buf); - commit = lookup_commit_or_die(sha1, "MERGE_HEAD"); - pptr = &commit_list_insert(commit, pptr)->next; + pptr = &commit_list_insert(parent, pptr)->next; } fclose(fp); strbuf_release(&m); @@ -1482,12 +1500,21 @@ int cmd_commit(int argc, const char **argv, const char *prefix) exit(1); } - if (commit_tree(sb.buf, active_cache_tree->sha1, parents, sha1, - author_ident.buf)) { + if (amend) { + const char *exclude_gpgsig[2] = { "gpgsig", NULL }; + extra = read_commit_extra_headers(current_head, exclude_gpgsig); + } else { + struct commit_extra_header **tail = &extra; + append_merge_tag_headers(parents, &tail); + } + + if (commit_tree_extended(&sb, active_cache_tree->sha1, parents, sha1, + author_ident.buf, sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } strbuf_release(&author_ident); + free_commit_extra_headers(extra); ref_lock = lock_any_ref_for_update("HEAD", !current_head diff --git a/builtin/diff.c b/builtin/diff.c index 0fe638fc4..387afa756 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -14,6 +14,7 @@ #include "log-tree.h" #include "builtin.h" #include "submodule.h" +#include "sha1-array.h" struct blobinfo { unsigned char sha1[20]; @@ -169,7 +170,7 @@ static int builtin_diff_combined(struct rev_info *revs, struct object_array_entry *ent, int ents) { - const unsigned char (*parent)[20]; + struct sha1_array parents = SHA1_ARRAY_INIT; int i; if (argc > 1) @@ -177,12 +178,11 @@ static int builtin_diff_combined(struct rev_info *revs, if (!revs->dense_combined_merges && !revs->combine_merges) revs->dense_combined_merges = revs->combine_merges = 1; - parent = xmalloc(ents * sizeof(*parent)); - for (i = 0; i < ents; i++) - hashcpy((unsigned char *)(parent + i), ent[i].item->sha1); - diff_tree_combined(parent[0], parent + 1, ents - 1, + for (i = 1; i < ents; i++) + sha1_array_append(&parents, ent[i].item->sha1); + diff_tree_combined(ent[0].item->sha1, &parents, revs->dense_combined_merges, revs); - free((void *)parent); + sha1_array_clear(&parents); return 0; } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9836e6b7c..08fed989a 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -25,7 +25,7 @@ static const char *fast_export_usage[] = { static int progress; static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; -static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; +static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ERROR; static int fake_missing_tagger; static int use_done_feature; static int no_data; @@ -51,7 +51,7 @@ static int parse_opt_tag_of_filtered_mode(const struct option *opt, const char *arg, int unset) { if (unset || !strcmp(arg, "abort")) - tag_of_filtered_mode = ABORT; + tag_of_filtered_mode = ERROR; else if (!strcmp(arg, "drop")) tag_of_filtered_mode = DROP; else if (!strcmp(arg, "rewrite")) diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 6207ecd29..a4d3e90a8 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -736,7 +736,7 @@ static int get_pack(int xd[2], char **pack_lockfile) } else { *av++ = "unpack-objects"; - if (args.quiet) + if (args.quiet || args.no_progress) *av++ = "-q"; } if (*hdr_arg) diff --git a/builtin/fetch.c b/builtin/fetch.c index 8761a33b4..8ec4eae3e 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -30,7 +30,7 @@ enum { }; static int all, append, dry_run, force, keep, multiple, prune, update_head_ok, verbosity; -static int progress, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT; static int tags = TAGS_DEFAULT; static const char *depth; static const char *upload_pack; @@ -78,7 +78,7 @@ static struct option builtin_fetch_options[] = { OPT_BOOLEAN('k', "keep", &keep, "keep downloaded pack"), OPT_BOOLEAN('u', "update-head-ok", &update_head_ok, "allow updating of HEAD ref"), - OPT_BOOLEAN(0, "progress", &progress, "force progress reporting"), + OPT_BOOL(0, "progress", &progress, "force progress reporting"), OPT_STRING(0, "depth", &depth, "depth", "deepen history of shallow clone"), { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, "dir", @@ -377,6 +377,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url, *filename = dry_run ? "/dev/null" : git_path("FETCH_HEAD"); + int want_merge; fp = fopen(filename, "a"); if (!fp) @@ -393,85 +394,95 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, goto abort; } - for (rm = ref_map; rm; rm = rm->next) { - struct ref *ref = NULL; - - if (rm->peer_ref) { - ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); - strcpy(ref->name, rm->peer_ref->name); - hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); - hashcpy(ref->new_sha1, rm->old_sha1); - ref->force = rm->peer_ref->force; - } + /* + * The first pass writes objects to be merged and then the + * second pass writes the rest, in order to allow using + * FETCH_HEAD as a refname to refer to the ref to be merged. + */ + for (want_merge = 1; 0 <= want_merge; want_merge--) { + for (rm = ref_map; rm; rm = rm->next) { + struct ref *ref = NULL; + + commit = lookup_commit_reference_gently(rm->old_sha1, 1); + if (!commit) + rm->merge = 0; + + if (rm->merge != want_merge) + continue; + + if (rm->peer_ref) { + ref = xcalloc(1, sizeof(*ref) + strlen(rm->peer_ref->name) + 1); + strcpy(ref->name, rm->peer_ref->name); + hashcpy(ref->old_sha1, rm->peer_ref->old_sha1); + hashcpy(ref->new_sha1, rm->old_sha1); + ref->force = rm->peer_ref->force; + } - commit = lookup_commit_reference_gently(rm->old_sha1, 1); - if (!commit) - rm->merge = 0; - if (!strcmp(rm->name, "HEAD")) { - kind = ""; - what = ""; - } - else if (!prefixcmp(rm->name, "refs/heads/")) { - kind = "branch"; - what = rm->name + 11; - } - else if (!prefixcmp(rm->name, "refs/tags/")) { - kind = "tag"; - what = rm->name + 10; - } - else if (!prefixcmp(rm->name, "refs/remotes/")) { - kind = "remote-tracking branch"; - what = rm->name + 13; - } - else { - kind = ""; - what = rm->name; - } + if (!strcmp(rm->name, "HEAD")) { + kind = ""; + what = ""; + } + else if (!prefixcmp(rm->name, "refs/heads/")) { + kind = "branch"; + what = rm->name + 11; + } + else if (!prefixcmp(rm->name, "refs/tags/")) { + kind = "tag"; + what = rm->name + 10; + } + else if (!prefixcmp(rm->name, "refs/remotes/")) { + kind = "remote-tracking branch"; + what = rm->name + 13; + } + else { + kind = ""; + what = rm->name; + } - url_len = strlen(url); - for (i = url_len - 1; url[i] == '/' && 0 <= i; i--) - ; - url_len = i + 1; - if (4 < i && !strncmp(".git", url + i - 3, 4)) - url_len = i - 3; - - strbuf_reset(¬e); - if (*what) { - if (*kind) - strbuf_addf(¬e, "%s ", kind); - strbuf_addf(¬e, "'%s' of ", what); - } - fprintf(fp, "%s\t%s\t%s", - sha1_to_hex(commit ? commit->object.sha1 : - rm->old_sha1), - rm->merge ? "" : "not-for-merge", - note.buf); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); - - strbuf_reset(¬e); - if (ref) { - rc |= update_local_ref(ref, what, ¬e); - free(ref); - } else - strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD", - TRANSPORT_SUMMARY_WIDTH, - *kind ? kind : "branch", - REFCOL_WIDTH, - *what ? what : "HEAD"); - if (note.len) { - if (verbosity >= 0 && !shown_url) { - fprintf(stderr, _("From %.*s\n"), - url_len, url); - shown_url = 1; + url_len = strlen(url); + for (i = url_len - 1; url[i] == '/' && 0 <= i; i--) + ; + url_len = i + 1; + if (4 < i && !strncmp(".git", url + i - 3, 4)) + url_len = i - 3; + + strbuf_reset(¬e); + if (*what) { + if (*kind) + strbuf_addf(¬e, "%s ", kind); + strbuf_addf(¬e, "'%s' of ", what); + } + fprintf(fp, "%s\t%s\t%s", + sha1_to_hex(rm->old_sha1), + rm->merge ? "" : "not-for-merge", + note.buf); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); + + strbuf_reset(¬e); + if (ref) { + rc |= update_local_ref(ref, what, ¬e); + free(ref); + } else + strbuf_addf(¬e, "* %-*s %-*s -> FETCH_HEAD", + TRANSPORT_SUMMARY_WIDTH, + *kind ? kind : "branch", + REFCOL_WIDTH, + *what ? what : "HEAD"); + if (note.len) { + if (verbosity >= 0 && !shown_url) { + fprintf(stderr, _("From %.*s\n"), + url_len, url); + shown_url = 1; + } + if (verbosity >= 0) + fprintf(stderr, " %s\n", note.buf); } - if (verbosity >= 0) - fprintf(stderr, " %s\n", note.buf); } } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 7e2f22589..c81a7fef2 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -5,32 +5,43 @@ #include "revision.h" #include "tag.h" #include "string-list.h" +#include "branch.h" +#include "fmt-merge-msg.h" +#include "gpg-interface.h" static const char * const fmt_merge_msg_usage[] = { "git fmt-merge-msg [-m ] [--log[=]|--no-log] [--file ]", NULL }; -static int shortlog_len; +static int use_branch_desc; -static int fmt_merge_msg_config(const char *key, const char *value, void *cb) +int fmt_merge_msg_config(const char *key, const char *value, void *cb) { if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { int is_bool; - shortlog_len = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool && shortlog_len < 0) + merge_log_config = git_config_bool_or_int(key, value, &is_bool); + if (!is_bool && merge_log_config < 0) return error("%s: negative length %s", key, value); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; + if (is_bool && merge_log_config) + merge_log_config = DEFAULT_MERGE_LOG_LEN; + } else if (!strcmp(key, "merge.branchdesc")) { + use_branch_desc = git_config_bool(key, value); } return 0; } +/* merge data per repository where the merged tips came from */ struct src_data { struct string_list branch, tag, r_branch, generic; int head_status; }; +struct origin_data { + unsigned char sha1[20]; + unsigned is_local_branch:1; +}; + static void init_src_data(struct src_data *data) { data->branch.strdup_strings = 1; @@ -45,7 +56,7 @@ static struct string_list origins = STRING_LIST_INIT_DUP; static int handle_line(char *line) { int i, len = strlen(line); - unsigned char *sha1; + struct origin_data *origin_data; char *src, *origin; struct src_data *src_data; struct string_list_item *item; @@ -61,16 +72,23 @@ static int handle_line(char *line) return 2; line[40] = 0; - sha1 = xmalloc(20); - i = get_sha1(line, sha1); + origin_data = xcalloc(1, sizeof(struct origin_data)); + i = get_sha1(line, origin_data->sha1); line[40] = '\t'; - if (i) + if (i) { + free(origin_data); return 3; + } if (line[len - 1] == '\n') line[len - 1] = 0; line += 42; + /* + * At this point, line points at the beginning of comment e.g. + * "branch 'frotz' of git://that/repository.git". + * Find the repository name and point it with src. + */ src = strstr(line, " of "); if (src) { *src = 0; @@ -93,6 +111,7 @@ static int handle_line(char *line) origin = src; src_data->head_status |= 1; } else if (!prefixcmp(line, "branch ")) { + origin_data->is_local_branch = 1; origin = line + 7; string_list_append(&src_data->branch, origin); src_data->head_status |= 2; @@ -119,7 +138,9 @@ static int handle_line(char *line) sprintf(new_origin, "%s of %s", origin, src); origin = new_origin; } - string_list_append(&origins, origin)->util = sha1; + if (strcmp(".", src)) + origin_data->is_local_branch = 0; + string_list_append(&origins, origin)->util = origin_data; return 0; } @@ -140,9 +161,30 @@ static void print_joined(const char *singular, const char *plural, } } -static void shortlog(const char *name, unsigned char *sha1, - struct commit *head, struct rev_info *rev, int limit, - struct strbuf *out) +static void add_branch_desc(struct strbuf *out, const char *name) +{ + struct strbuf desc = STRBUF_INIT; + + if (!read_branch_desc(&desc, name)) { + const char *bp = desc.buf; + while (*bp) { + const char *ep = strchrnul(bp, '\n'); + if (*ep) + ep++; + strbuf_addf(out, " : %.*s", (int)(ep - bp), bp); + bp = ep; + } + if (out->buf[out->len - 1] != '\n') + strbuf_addch(out, '\n'); + } + strbuf_release(&desc); +} + +static void shortlog(const char *name, + struct origin_data *origin_data, + struct commit *head, + struct rev_info *rev, int limit, + struct strbuf *out) { int i, count = 0; struct commit *commit; @@ -150,6 +192,7 @@ static void shortlog(const char *name, unsigned char *sha1, struct string_list subjects = STRING_LIST_INIT_DUP; int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; struct strbuf sb = STRBUF_INIT; + const unsigned char *sha1 = origin_data->sha1; branch = deref_tag(parse_object(sha1), sha1_to_hex(sha1), 40); if (!branch || branch->type != OBJ_COMMIT) @@ -188,6 +231,9 @@ static void shortlog(const char *name, unsigned char *sha1, else strbuf_addf(out, "\n* %s:\n", name); + if (origin_data->is_local_branch && use_branch_desc) + add_branch_desc(out, name); + for (i = 0; i < subjects.nr; i++) if (i >= limit) strbuf_addf(out, " ...\n"); @@ -203,7 +249,7 @@ static void shortlog(const char *name, unsigned char *sha1, string_list_clear(&subjects, 0); } -static void do_fmt_merge_msg_title(struct strbuf *out, +static void fmt_merge_msg_title(struct strbuf *out, const char *current_branch) { int i = 0; char *sep = ""; @@ -256,14 +302,81 @@ static void do_fmt_merge_msg_title(struct strbuf *out, strbuf_addf(out, " into %s\n", current_branch); } -static int do_fmt_merge_msg(int merge_title, struct strbuf *in, - struct strbuf *out, int shortlog_len) { +static void fmt_tag_signature(struct strbuf *tagbuf, + struct strbuf *sig, + const char *buf, + unsigned long len) +{ + const char *tag_body = strstr(buf, "\n\n"); + if (tag_body) { + tag_body += 2; + strbuf_add(tagbuf, tag_body, buf + len - tag_body); + } + strbuf_complete_line(tagbuf); + strbuf_add_lines(tagbuf, "# ", sig->buf, sig->len); +} + +static void fmt_merge_msg_sigs(struct strbuf *out) +{ + int i, tag_number = 0, first_tag = 0; + struct strbuf tagbuf = STRBUF_INIT; + + for (i = 0; i < origins.nr; i++) { + unsigned char *sha1 = origins.items[i].util; + enum object_type type; + unsigned long size, len; + char *buf = read_sha1_file(sha1, &type, &size); + struct strbuf sig = STRBUF_INIT; + + if (!buf || type != OBJ_TAG) + goto next; + len = parse_signature(buf, size); + + if (size == len) + ; /* merely annotated */ + else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig)) { + if (!sig.len) + strbuf_addstr(&sig, "gpg verification failed.\n"); + } + + if (!tag_number++) { + fmt_tag_signature(&tagbuf, &sig, buf, len); + first_tag = i; + } else { + if (tag_number == 2) { + struct strbuf tagline = STRBUF_INIT; + strbuf_addf(&tagline, "\n# %s\n", + origins.items[first_tag].string); + strbuf_insert(&tagbuf, 0, tagline.buf, + tagline.len); + strbuf_release(&tagline); + } + strbuf_addf(&tagbuf, "\n# %s\n", + origins.items[i].string); + fmt_tag_signature(&tagbuf, &sig, buf, len); + } + strbuf_release(&sig); + next: + free(buf); + } + if (tagbuf.len) { + strbuf_addch(out, '\n'); + strbuf_addbuf(out, &tagbuf); + } + strbuf_release(&tagbuf); +} + +int fmt_merge_msg(struct strbuf *in, struct strbuf *out, + struct fmt_merge_msg_opts *opts) +{ int i = 0, pos = 0; unsigned char head_sha1[20]; const char *current_branch; + void *current_branch_to_free; /* get current branch */ - current_branch = resolve_ref("HEAD", head_sha1, 1, NULL); + current_branch = current_branch_to_free = + resolve_refdup("HEAD", head_sha1, 1, NULL); if (!current_branch) die("No current branch"); if (!prefixcmp(current_branch, "refs/heads/")) @@ -283,13 +396,13 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in, die ("Error in line %d: %.*s", i, len, p); } - if (!srcs.nr) - return 0; + if (opts->add_title && srcs.nr) + fmt_merge_msg_title(out, current_branch); - if (merge_title) - do_fmt_merge_msg_title(out, current_branch); + if (origins.nr) + fmt_merge_msg_sigs(out); - if (shortlog_len) { + if (opts->shortlog_len) { struct commit *head; struct rev_info rev; @@ -303,21 +416,21 @@ static int do_fmt_merge_msg(int merge_title, struct strbuf *in, strbuf_addch(out, '\n'); for (i = 0; i < origins.nr; i++) - shortlog(origins.items[i].string, origins.items[i].util, - head, &rev, shortlog_len, out); + shortlog(origins.items[i].string, + origins.items[i].util, + head, &rev, opts->shortlog_len, out); } - return 0; -} -int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - int merge_title, int shortlog_len) { - return do_fmt_merge_msg(merge_title, in, out, shortlog_len); + strbuf_complete_line(out); + free(current_branch_to_free); + return 0; } int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; const char *message = NULL; + int shortlog_len = -1; struct option options[] = { { OPTION_INTEGER, 0, "log", &shortlog_len, "n", "populate log with at most entries from shortlog", @@ -335,20 +448,15 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) FILE *in = stdin; struct strbuf input = STRBUF_INIT, output = STRBUF_INIT; int ret; + struct fmt_merge_msg_opts opts; git_config(fmt_merge_msg_config, NULL); argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); - if (message && !shortlog_len) { - char nl = '\n'; - write_in_full(STDOUT_FILENO, message, strlen(message)); - write_in_full(STDOUT_FILENO, &nl, 1); - return 0; - } if (shortlog_len < 0) - die("Negative --log=%d", shortlog_len); + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; if (inpath && strcmp(inpath, "-")) { in = fopen(inpath, "r"); @@ -361,10 +469,12 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) if (message) strbuf_addstr(&output, message); - ret = fmt_merge_msg(&input, &output, - message ? 0 : 1, - shortlog_len); + memset(&opts, 0, sizeof(opts)); + opts.add_title = !message; + opts.shortlog_len = shortlog_len; + + ret = fmt_merge_msg(&input, &output, &opts); if (ret) return ret; write_in_full(STDOUT_FILENO, output.buf, output.len); diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index d90e5d2b2..b01d76a24 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -628,11 +628,8 @@ static void populate_value(struct refinfo *ref) if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) { unsigned char unused1[20]; - const char *symref; - symref = resolve_ref(ref->refname, unused1, 1, NULL); - if (symref) - ref->symref = xstrdup(symref); - else + ref->symref = resolve_refdup(ref->refname, unused1, 1, NULL); + if (!ref->symref) ref->symref = ""; } diff --git a/builtin/fsck.c b/builtin/fsck.c index df1a88b51..8c479a791 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -11,6 +11,7 @@ #include "fsck.h" #include "parse-options.h" #include "dir.h" +#include "progress.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -27,8 +28,10 @@ static const char *head_points_at; static int errors_found; static int write_lost_and_found; static int verbose; +static int show_progress = -1; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 +#define ERROR_PACK 04 #ifdef NO_D_INO_IN_DIRENT #define SORT_DIRENT 0 @@ -137,7 +140,11 @@ static int traverse_one_object(struct object *obj) static int traverse_reachable(void) { + struct progress *progress = NULL; + unsigned int nr = 0; int result = 0; + if (show_progress) + progress = start_progress_delay("Checking connectivity", 0, 0, 2); while (pending.nr) { struct object_array_entry *entry; struct object *obj; @@ -145,7 +152,9 @@ static int traverse_reachable(void) entry = pending.objects + --pending.nr; obj = entry->item; result |= traverse_one_object(obj); + display_progress(progress, ++nr); } + stop_progress(&progress); return !!result; } @@ -281,14 +290,8 @@ static void check_connectivity(void) } } -static int fsck_sha1(const unsigned char *sha1) +static int fsck_obj(struct object *obj) { - struct object *obj = parse_object(sha1); - if (!obj) { - errors_found |= ERROR_OBJECT; - return error("%s: object corrupt or missing", - sha1_to_hex(sha1)); - } if (obj->flags & SEEN) return 0; obj->flags |= SEEN; @@ -331,6 +334,29 @@ static int fsck_sha1(const unsigned char *sha1) return 0; } +static int fsck_sha1(const unsigned char *sha1) +{ + struct object *obj = parse_object(sha1); + if (!obj) { + errors_found |= ERROR_OBJECT; + return error("%s: object corrupt or missing", + sha1_to_hex(sha1)); + } + return fsck_obj(obj); +} + +static int fsck_obj_buffer(const unsigned char *sha1, enum object_type type, + unsigned long size, void *buffer, int *eaten) +{ + struct object *obj; + obj = parse_object_buffer(sha1, type, size, buffer, eaten); + if (!obj) { + errors_found |= ERROR_OBJECT; + return error("%s: object corrupt or missing", sha1_to_hex(sha1)); + } + return fsck_obj(obj); +} + /* * This is the sorting chunk size: make it reasonably * big so that we can sort well.. @@ -512,15 +538,20 @@ static void get_default_heads(void) static void fsck_object_dir(const char *path) { int i; + struct progress *progress = NULL; if (verbose) fprintf(stderr, "Checking object directory\n"); + if (show_progress) + progress = start_progress("Checking object directories", 256); for (i = 0; i < 256; i++) { static char dir[4096]; sprintf(dir, "%s/%02x", path, i); fsck_dir(i, dir); + display_progress(progress, i+1); } + stop_progress(&progress); fsck_sha1_list(); } @@ -532,7 +563,7 @@ static int fsck_head_link(void) if (verbose) fprintf(stderr, "Checking HEAD link\n"); - head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag); + head_points_at = resolve_ref_unsafe("HEAD", head_sha1, 0, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) @@ -591,6 +622,7 @@ static struct option fsck_opts[] = { OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"), OPT_BOOLEAN(0, "lost-found", &write_lost_and_found, "write dangling objects in .git/lost-found"), + OPT_BOOL(0, "progress", &show_progress, "show progress"), OPT_END(), }; @@ -603,6 +635,12 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) read_replace_refs = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); + + if (show_progress == -1) + show_progress = isatty(2); + if (verbose) + show_progress = 0; + if (write_lost_and_found) { check_full = 1; include_reflogs = 0; @@ -622,20 +660,28 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (check_full) { struct packed_git *p; + uint32_t total = 0, count = 0; + struct progress *progress = NULL; prepare_packed_git(); - for (p = packed_git; p; p = p->next) - /* verify gives error messages itself */ - verify_pack(p); + if (show_progress) { + for (p = packed_git; p; p = p->next) { + if (open_pack_index(p)) + continue; + total += p->num_objects; + } + + progress = start_progress("Checking objects", total); + } for (p = packed_git; p; p = p->next) { - uint32_t j, num; - if (open_pack_index(p)) - continue; - num = p->num_objects; - for (j = 0; j < num; j++) - fsck_sha1(nth_packed_object_sha1(p, j)); + /* verify gives error messages itself */ + if (verify_pack(p, fsck_obj_buffer, + progress, count)) + errors_found |= ERROR_PACK; + count += p->num_objects; } + stop_progress(&progress); } heads = 0; diff --git a/builtin/gc.c b/builtin/gc.c index 049809471..271376d82 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -32,7 +32,7 @@ static const char *prune_expire = "2.weeks.ago"; static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL}; static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL}; static const char *argv_repack[MAX_ADD] = {"repack", "-d", "-l", NULL}; -static const char *argv_prune[] = {"prune", "--expire", NULL, NULL}; +static const char *argv_prune[] = {"prune", "--expire", NULL, NULL, NULL}; static const char *argv_rerere[] = {"rerere", "gc", NULL}; static int gc_config(const char *var, const char *value, void *cb) @@ -243,6 +243,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (prune_expire) { argv_prune[2] = prune_expire; + if (quiet) + argv_prune[3] = "--no-progress"; if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) return error(FAILED_RUN, argv_prune[0]); } diff --git a/builtin/grep.c b/builtin/grep.c index 988ea1d33..9fc3e95cc 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -17,7 +17,6 @@ #include "grep.h" #include "quote.h" #include "dir.h" -#include "thread-utils.h" static char const * const grep_usage[] = { "git grep [options] [-e] [...] [[--] ...]", @@ -30,25 +29,12 @@ static int use_threads = 1; #define THREADS 8 static pthread_t threads[THREADS]; -static void *load_sha1(const unsigned char *sha1, unsigned long *size, - const char *name); -static void *load_file(const char *filename, size_t *sz); - -enum work_type {WORK_SHA1, WORK_FILE}; - /* We use one producer thread and THREADS consumer * threads. The producer adds struct work_items to 'todo' and the * consumers pick work items from the same array. */ struct work_item { - enum work_type type; - char *name; - - /* if type == WORK_SHA1, then 'identifier' is a SHA1, - * otherwise type == WORK_FILE, and 'identifier' is a NUL - * terminated filename. - */ - void *identifier; + struct grep_source source; char done; struct strbuf out; }; @@ -86,21 +72,6 @@ static inline void grep_unlock(void) pthread_mutex_unlock(&grep_mutex); } -/* Used to serialize calls to read_sha1_file. */ -static pthread_mutex_t read_sha1_mutex; - -static inline void read_sha1_lock(void) -{ - if (use_threads) - pthread_mutex_lock(&read_sha1_mutex); -} - -static inline void read_sha1_unlock(void) -{ - if (use_threads) - pthread_mutex_unlock(&read_sha1_mutex); -} - /* Signalled when a new work_item is added to todo. */ static pthread_cond_t cond_add; @@ -114,7 +85,8 @@ static pthread_cond_t cond_result; static int skip_first_line; -static void add_work(enum work_type type, char *name, void *id) +static void add_work(struct grep_opt *opt, enum grep_source_type type, + const char *name, const void *id) { grep_lock(); @@ -122,9 +94,9 @@ static void add_work(enum work_type type, char *name, void *id) pthread_cond_wait(&cond_write, &grep_mutex); } - todo[todo_end].type = type; - todo[todo_end].name = name; - todo[todo_end].identifier = id; + grep_source_init(&todo[todo_end].source, type, name, id); + if (opt->binary != GREP_BINARY_TEXT) + grep_source_load_driver(&todo[todo_end].source); todo[todo_end].done = 0; strbuf_reset(&todo[todo_end].out); todo_end = (todo_end + 1) % ARRAY_SIZE(todo); @@ -152,21 +124,6 @@ static struct work_item *get_work(void) return ret; } -static void grep_sha1_async(struct grep_opt *opt, char *name, - const unsigned char *sha1) -{ - unsigned char *s; - s = xmalloc(20); - memcpy(s, sha1, 20); - add_work(WORK_SHA1, name, s); -} - -static void grep_file_async(struct grep_opt *opt, char *name, - const char *filename) -{ - add_work(WORK_FILE, name, xstrdup(filename)); -} - static void work_done(struct work_item *w) { int old_done; @@ -193,8 +150,7 @@ static void work_done(struct work_item *w) write_or_die(1, p, len); } - free(w->name); - free(w->identifier); + grep_source_clear(&w->source); } if (old_done != todo_done) @@ -217,25 +173,8 @@ static void *run(void *arg) break; opt->output_priv = w; - if (w->type == WORK_SHA1) { - unsigned long sz; - void* data = load_sha1(w->identifier, &sz, w->name); - - if (data) { - hit |= grep_buffer(opt, w->name, data, sz); - free(data); - } - } else if (w->type == WORK_FILE) { - size_t sz; - void* data = load_file(w->identifier, &sz); - if (data) { - hit |= grep_buffer(opt, w->name, data, sz); - free(data); - } - } else { - assert(0); - } - + hit |= grep_source(opt, &w->source); + grep_source_clear_data(&w->source); work_done(w); } free_grep_patterns(arg); @@ -255,10 +194,12 @@ static void start_threads(struct grep_opt *opt) int i; pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&read_sha1_mutex, NULL); + pthread_mutex_init(&grep_read_mutex, NULL); + pthread_mutex_init(&grep_attr_mutex, NULL); pthread_cond_init(&cond_add, NULL); pthread_cond_init(&cond_write, NULL); pthread_cond_init(&cond_result, NULL); + grep_use_locks = 1; for (i = 0; i < ARRAY_SIZE(todo); i++) { strbuf_init(&todo[i].out, 0); @@ -302,16 +243,16 @@ static int wait_all(void) } pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&read_sha1_mutex); + pthread_mutex_destroy(&grep_read_mutex); + pthread_mutex_destroy(&grep_attr_mutex); pthread_cond_destroy(&cond_add); pthread_cond_destroy(&cond_write); pthread_cond_destroy(&cond_result); + grep_use_locks = 0; return hit; } #else /* !NO_PTHREADS */ -#define read_sha1_lock() -#define read_sha1_unlock() static int wait_all(void) { @@ -373,21 +314,9 @@ static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type { void *data; - read_sha1_lock(); + grep_read_lock(); data = read_sha1_file(sha1, type, size); - read_sha1_unlock(); - return data; -} - -static void *load_sha1(const unsigned char *sha1, unsigned long *size, - const char *name) -{ - enum object_type type; - void *data = lock_and_read_sha1_file(sha1, &type, size); - - if (!data) - error(_("'%s': unable to read %s"), name, sha1_to_hex(sha1)); - + grep_read_unlock(); return data; } @@ -395,7 +324,6 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *filename, int tree_name_len) { struct strbuf pathbuf = STRBUF_INIT; - char *name; if (opt->relative && opt->prefix_length) { quote_path_relative(filename + tree_name_len, -1, &pathbuf, @@ -405,87 +333,51 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, strbuf_addstr(&pathbuf, filename); } - name = strbuf_detach(&pathbuf, NULL); - #ifndef NO_PTHREADS if (use_threads) { - grep_sha1_async(opt, name, sha1); + add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + strbuf_release(&pathbuf); return 0; } else #endif { + struct grep_source gs; int hit; - unsigned long sz; - void *data = load_sha1(sha1, &sz, name); - if (!data) - hit = 0; - else - hit = grep_buffer(opt, name, data, sz); - - free(data); - free(name); - return hit; - } -} -static void *load_file(const char *filename, size_t *sz) -{ - struct stat st; - char *data; - int i; + grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, sha1); + strbuf_release(&pathbuf); + hit = grep_source(opt, &gs); - if (lstat(filename, &st) < 0) { - err_ret: - if (errno != ENOENT) - error(_("'%s': %s"), filename, strerror(errno)); - return NULL; - } - if (!S_ISREG(st.st_mode)) - return NULL; - *sz = xsize_t(st.st_size); - i = open(filename, O_RDONLY); - if (i < 0) - goto err_ret; - data = xmalloc(*sz + 1); - if (st.st_size != read_in_full(i, data, *sz)) { - error(_("'%s': short read %s"), filename, strerror(errno)); - close(i); - free(data); - return NULL; + grep_source_clear(&gs); + return hit; } - close(i); - data[*sz] = 0; - return data; } static int grep_file(struct grep_opt *opt, const char *filename) { struct strbuf buf = STRBUF_INIT; - char *name; if (opt->relative && opt->prefix_length) quote_path_relative(filename, -1, &buf, opt->prefix); else strbuf_addstr(&buf, filename); - name = strbuf_detach(&buf, NULL); #ifndef NO_PTHREADS if (use_threads) { - grep_file_async(opt, name, filename); + add_work(opt, GREP_SOURCE_FILE, buf.buf, filename); + strbuf_release(&buf); return 0; } else #endif { + struct grep_source gs; int hit; - size_t sz; - void *data = load_file(filename, &sz); - if (!data) - hit = 0; - else - hit = grep_buffer(opt, name, data, sz); - free(data); - free(name); + grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename); + strbuf_release(&buf); + hit = grep_source(opt, &gs); + + grep_source_clear(&gs); return hit; } } @@ -614,10 +506,10 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct strbuf base; int hit, len; - read_sha1_lock(); + grep_read_lock(); data = read_object_with_reference(obj->sha1, tree_type, &size, NULL); - read_sha1_unlock(); + grep_read_unlock(); if (!data) die(_("unable to read tree (%s)"), sha1_to_hex(obj->sha1)); @@ -1001,20 +893,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!opt.fixed && opt.ignore_case) opt.regflags |= REG_ICASE; -#ifndef NO_PTHREADS - if (online_cpus() == 1 || !grep_threads_ok(&opt)) - use_threads = 0; - - if (use_threads) { - if (opt.pre_context || opt.post_context || opt.file_break || - opt.funcbody) - skip_first_line = 1; - start_threads(&opt); - } -#else - use_threads = 0; -#endif - compile_grep_patterns(&opt); /* Check revs and then paths */ @@ -1036,6 +914,23 @@ int cmd_grep(int argc, const char **argv, const char *prefix) break; } +#ifndef NO_PTHREADS + if (list.nr || cached || online_cpus() == 1) + use_threads = 0; +#else + use_threads = 0; +#endif + +#ifndef NO_PTHREADS + if (use_threads) { + if (!(opt.name_only || opt.unmatch_name_only || opt.count) + && (opt.pre_context || opt.post_context || + opt.file_break || opt.funcbody)) + skip_first_line = 1; + start_threads(&opt); + } +#endif + /* The rest are paths */ if (!seen_dashdash) { int j; diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 98025da76..af7dc37a4 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -172,10 +172,10 @@ static const char *open_pack_file(const char *pack_name) if (from_stdin) { input_fd = 0; if (!pack_name) { - static char tmpfile[PATH_MAX]; - output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile), + static char tmp_file[PATH_MAX]; + output_fd = odb_mkstemp(tmp_file, sizeof(tmp_file), "pack/tmp_pack_XXXXXX"); - pack_name = xstrdup(tmpfile); + pack_name = xstrdup(tmp_file); } else output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); if (output_fd < 0) diff --git a/builtin/log.c b/builtin/log.c index 56bc555d1..7d1f6f88a 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -19,6 +19,7 @@ #include "remote.h" #include "string-list.h" #include "parse-options.h" +#include "branch.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -744,10 +745,24 @@ static void print_signature(void) printf("-- \n%s\n\n", signature); } +static void add_branch_description(struct strbuf *buf, const char *branch_name) +{ + struct strbuf desc = STRBUF_INIT; + if (!branch_name || !*branch_name) + return; + read_branch_desc(&desc, branch_name); + if (desc.len) { + strbuf_addch(buf, '\n'); + strbuf_add(buf, desc.buf, desc.len); + strbuf_addch(buf, '\n'); + } +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, int numbered, int numbered_files, struct commit *origin, int nr, struct commit **list, struct commit *head, + const char *branch_name, int quiet) { const char *committer; @@ -805,6 +820,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, pp_user_info(&pp, NULL, &sb, committer, encoding); pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); pp_remainder(&pp, &msg, &sb, 0); + add_branch_description(&sb, branch_name); printf("%s\n", sb.buf); strbuf_release(&sb); @@ -1004,6 +1020,35 @@ static int cc_callback(const struct option *opt, const char *arg, int unset) return 0; } +static char *find_branch_name(struct rev_info *rev) +{ + int i, positive = -1; + unsigned char branch_sha1[20]; + struct strbuf buf = STRBUF_INIT; + const char *branch; + + for (i = 0; i < rev->cmdline.nr; i++) { + if (rev->cmdline.rev[i].flags & UNINTERESTING) + continue; + if (positive < 0) + positive = i; + else + return NULL; + } + if (positive < 0) + return NULL; + strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name); + branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL); + if (!branch || + prefixcmp(branch, "refs/heads/") || + hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1)) + branch = NULL; + strbuf_release(&buf); + if (branch) + return xstrdup(rev->cmdline.rev[positive].name); + return NULL; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; @@ -1025,6 +1070,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; + char *branch_name = NULL; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, "use [PATCH n/m] even with a single patch", @@ -1215,8 +1261,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * origin" that prepares what the origin side still * does not have. */ + unsigned char sha1[20]; + const char *ref; + rev.pending.objects[0].item->flags |= UNINTERESTING; add_head_to_pending(&rev); + ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL); + if (ref && !prefixcmp(ref, "refs/heads/")) + branch_name = xstrdup(ref + strlen("refs/heads/")); + else + branch_name = xstrdup(""); /* no branch */ } /* * Otherwise, it is "format-patch -22 HEAD", and/or @@ -1232,16 +1286,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.show_root_diff = 1; if (cover_letter) { - /* remember the range */ + /* + * NEEDSWORK:randomly pick one positive commit to show + * diffstat; this is often the tip and the command + * happens to do the right thing in most cases, but a + * complex command like "--cover-letter a b c ^bottom" + * picks "c" and shows diffstat between bottom..c + * which may not match what the series represents at + * all and totally broken. + */ int i; for (i = 0; i < rev.pending.nr; i++) { struct object *o = rev.pending.objects[i].item; if (!(o->flags & UNINTERESTING)) head = (struct commit *)o; } - /* We can't generate a cover letter without any patches */ + /* There is nothing to show; it is not an error, though. */ if (!head) return 0; + if (!branch_name) + branch_name = find_branch_name(&rev); } if (ignore_if_in_upstream) { @@ -1292,7 +1356,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, use_stdout, numbered, numbered_files, - origin, nr, list, head, quiet); + origin, nr, list, head, branch_name, quiet); total++; start_number--; } @@ -1364,6 +1428,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) fclose(stdout); } free(list); + free(branch_name); string_list_clear(&extra_to, 0); string_list_clear(&extra_cc, 0); string_list_clear(&extra_hdr, 0); diff --git a/builtin/merge.c b/builtin/merge.c index 7d92e2064..5126443fd 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -26,6 +26,8 @@ #include "merge-recursive.h" #include "resolve-undo.h" #include "remote.h" +#include "fmt-merge-msg.h" +#include "gpg-interface.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -44,10 +46,11 @@ static const char * const builtin_merge_usage[] = { NULL }; -static int show_diffstat = 1, shortlog_len, squash; +static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = 1, allow_fast_forward = 1; -static int fast_forward_only, option_edit; +static int fast_forward_only, option_edit = -1; static int allow_trivial = 1, have_message; +static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct commit_list *remoteheads; static struct strategy **use_strategies; @@ -62,6 +65,7 @@ static int allow_rerere_auto; static int abort_current_merge; static int show_progress = -1; static int default_to_upstream; +static const char *sign_commit; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -189,7 +193,7 @@ static struct option builtin_merge_options[] = { "create a single commit instead of doing a merge"), OPT_BOOLEAN(0, "commit", &option_commit, "perform a commit if the merge succeeds (default)"), - OPT_BOOLEAN('e', "edit", &option_edit, + OPT_BOOL('e', "edit", &option_edit, "edit message before committing"), OPT_BOOLEAN(0, "ff", &allow_fast_forward, "allow fast-forward (default)"), @@ -207,6 +211,9 @@ static struct option builtin_merge_options[] = { OPT_BOOLEAN(0, "abort", &abort_current_merge, "abort the current in-progress merge"), OPT_SET_INT(0, "progress", &show_progress, "force progress reporting", 1), + { OPTION_STRING, 'S', "gpg-sign", &sign_commit, "key id", + "GPG sign commit", PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_BOOLEAN(0, "overwrite-ignore", &overwrite_ignore, "update ignored files (default)"), OPT_END() }; @@ -408,21 +415,11 @@ static void finish(struct commit *head_commit, strbuf_release(&reflog_message); } -static struct object *want_commit(const char *name) -{ - struct object *obj; - unsigned char sha1[20]; - if (get_sha1(name, sha1)) - return NULL; - obj = parse_object(sha1); - return peel_to_type(name, 0, obj, OBJ_COMMIT); -} - /* Get the name for the merge commit's message. */ static void merge_name(const char *remote, struct strbuf *msg) { - struct object *remote_head; - unsigned char branch_head[20], buf_sha[20]; + struct commit *remote_head; + unsigned char branch_head[20]; struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; @@ -433,7 +430,7 @@ static void merge_name(const char *remote, struct strbuf *msg) remote = bname.buf; memset(branch_head, 0, sizeof(branch_head)); - remote_head = want_commit(remote); + remote_head = get_merge_parent(remote); if (!remote_head) die(_("'%s' does not point to a commit"), remote); @@ -443,6 +440,11 @@ static void merge_name(const char *remote, struct strbuf *msg) sha1_to_hex(branch_head), remote); goto cleanup; } + if (!prefixcmp(found_ref, "refs/tags/")) { + strbuf_addf(msg, "%s\t\ttag '%s' of .\n", + sha1_to_hex(branch_head), remote); + goto cleanup; + } if (!prefixcmp(found_ref, "refs/remotes/")) { strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n", sha1_to_hex(branch_head), remote); @@ -481,10 +483,10 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_addstr(&truname, "refs/heads/"); strbuf_addstr(&truname, remote); strbuf_setlen(&truname, truname.len - len); - if (resolve_ref(truname.buf, buf_sha, 1, NULL)) { + if (ref_exists(truname.buf)) { strbuf_addf(msg, "%s\t\tbranch '%s'%s of .\n", - sha1_to_hex(remote_head->sha1), + sha1_to_hex(remote_head->object.sha1), truname.buf + 11, (early ? " (early part)" : "")); strbuf_release(&truname); @@ -514,7 +516,7 @@ static void merge_name(const char *remote, struct strbuf *msg) goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", - sha1_to_hex(remote_head->sha1), remote); + sha1_to_hex(remote_head->object.sha1), remote); cleanup: strbuf_release(&buf); strbuf_release(&bname); @@ -542,6 +544,8 @@ static void parse_branch_merge_options(char *bmo) static int git_merge_config(const char *k, const char *v, void *cb) { + int status; + if (branch && !prefixcmp(k, "branch.") && !prefixcmp(k + 7, branch) && !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { @@ -558,15 +562,7 @@ static int git_merge_config(const char *k, const char *v, void *cb) return git_config_string(&pull_octopus, k, v); else if (!strcmp(k, "merge.renormalize")) option_renormalize = git_config_bool(k, v); - else if (!strcmp(k, "merge.log") || !strcmp(k, "merge.summary")) { - int is_bool; - shortlog_len = git_config_bool_or_int(k, v, &is_bool); - if (!is_bool && shortlog_len < 0) - return error(_("%s: negative length %s"), k, v); - if (is_bool && shortlog_len) - shortlog_len = DEFAULT_MERGE_LOG_LEN; - return 0; - } else if (!strcmp(k, "merge.ff")) { + else if (!strcmp(k, "merge.ff")) { int boolval = git_config_maybe_bool(k, v); if (0 <= boolval) { allow_fast_forward = boolval; @@ -579,6 +575,13 @@ static int git_merge_config(const char *k, const char *v, void *cb) default_to_upstream = git_config_bool(k, v); return 0; } + + status = fmt_merge_msg_config(k, v, cb); + if (status) + return status; + status = git_gpg_config(k, v, NULL); + if (status) + return status; return git_diff_ui_config(k, v, cb); } @@ -718,7 +721,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); o.branch1 = head_arg; - o.branch2 = remoteheads->item->util; + o.branch2 = merge_remote_util(remoteheads->item)->name; for (j = common; j; j = j->next) commit_list_insert(j->item, &reversed); @@ -773,10 +776,12 @@ int checkout_fast_forward(const unsigned char *head, const unsigned char *remote memset(&trees, 0, sizeof(trees)); memset(&opts, 0, sizeof(opts)); memset(&t, 0, sizeof(t)); - memset(&dir, 0, sizeof(dir)); - dir.flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(&dir); - opts.dir = &dir; + if (overwrite_ignore) { + memset(&dir, 0, sizeof(dir)); + dir.flags |= DIR_SHOW_IGNORED; + setup_standard_excludes(&dir); + opts.dir = &dir; + } opts.head_idx = 1; opts.src_index = &the_index; @@ -880,11 +885,21 @@ static void abort_commit(const char *err_msg) exit(1); } +static const char merge_editor_comment[] = +N_("Please enter a commit message to explain why this merge is necessary,\n" + "especially if it merges an updated upstream into a topic branch.\n" + "\n" + "Lines starting with '#' will be ignored, and an empty message aborts\n" + "the commit.\n"); + static void prepare_to_commit(void) { struct strbuf msg = STRBUF_INIT; + const char *comment = _(merge_editor_comment); strbuf_addbuf(&msg, &merge_msg); strbuf_addch(&msg, '\n'); + if (0 < option_edit) + strbuf_add_lines(&msg, "# ", comment, strlen(comment)); write_merge_msg(&msg); run_hook(get_index_file(), "prepare-commit-msg", git_path("MERGE_MSG"), "merge", NULL, NULL); @@ -913,7 +928,9 @@ static int merge_trivial(struct commit *head) parent->next->item = remoteheads->item; parent->next->next = NULL; prepare_to_commit(); - commit_tree(merge_msg.buf, result_tree, parent, result_commit, NULL); + if (commit_tree(&merge_msg, result_tree, parent, result_commit, NULL, + sign_commit)) + die(_("failed to write commit object")); finish(head, result_commit, "In-index merge"); drop_save(); return 0; @@ -944,7 +961,9 @@ static int finish_automerge(struct commit *head, strbuf_addch(&merge_msg, '\n'); prepare_to_commit(); free_commit_list(remoteheads); - commit_tree(merge_msg.buf, result_tree, parents, result_commit, NULL); + if (commit_tree(&merge_msg, result_tree, parents, result_commit, + NULL, sign_commit)) + die(_("failed to write commit object")); strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy); finish(head, result_commit, buf.buf); strbuf_release(&buf); @@ -1058,9 +1077,16 @@ static void write_merge_state(void) struct commit_list *j; struct strbuf buf = STRBUF_INIT; - for (j = remoteheads; j; j = j->next) - strbuf_addf(&buf, "%s\n", - sha1_to_hex(j->item->object.sha1)); + for (j = remoteheads; j; j = j->next) { + unsigned const char *sha1; + struct commit *c = j->item; + if (c->util && merge_remote_util(c)->obj) { + sha1 = merge_remote_util(c)->obj->sha1; + } else { + sha1 = c->object.sha1; + } + strbuf_addf(&buf, "%s\n", sha1_to_hex(sha1)); + } filename = git_path("MERGE_HEAD"); fd = open(filename, O_WRONLY | O_CREAT, 0666); if (fd < 0) @@ -1083,6 +1109,33 @@ static void write_merge_state(void) close(fd); } +static int default_edit_option(void) +{ + static const char name[] = "GIT_MERGE_AUTOEDIT"; + const char *e = getenv(name); + struct stat st_stdin, st_stdout; + + if (have_message) + /* an explicit -m msg without --[no-]edit */ + return 0; + + if (e) { + int v = git_config_maybe_bool(name, e); + if (v < 0) + die("Bad value '%s' in environment '%s'", e, name); + return v; + } + + /* Use editor if stdin and stdout are the same and is a tty */ + return (!fstat(0, &st_stdin) && + !fstat(1, &st_stdout) && + isatty(0) && isatty(1) && + st_stdin.st_dev == st_stdout.st_dev && + st_stdin.st_ino == st_stdout.st_ino && + st_stdin.st_mode == st_stdout.st_mode); +} + + int cmd_merge(int argc, const char **argv, const char *prefix) { unsigned char result_tree[20]; @@ -1091,11 +1144,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit *head_commit; struct strbuf buf = STRBUF_INIT; const char *head_arg; - int flag, i; + int flag, i, ret = 0; int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; struct commit_list **remotes = &remoteheads; + void *branch_to_free; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); @@ -1104,7 +1158,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. */ - branch = resolve_ref("HEAD", head_sha1, 0, &flag); + branch = branch_to_free = resolve_refdup("HEAD", head_sha1, 0, &flag); if (branch && !prefixcmp(branch, "refs/heads/")) branch += 11; if (!branch || is_null_sha1(head_sha1)) @@ -1118,6 +1172,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) parse_branch_merge_options(branch_mergeoptions); argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); + if (shortlog_len < 0) + shortlog_len = (merge_log_config > 0) ? merge_log_config : 0; if (verbosity < 0 && show_progress == -1) show_progress = 0; @@ -1130,7 +1186,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("There is no merge to abort (MERGE_HEAD missing).")); /* Invoke 'git reset --merge' */ - return cmd_reset(nargc, nargv, prefix); + ret = cmd_reset(nargc, nargv, prefix); + goto done; } if (read_cache_unmerged()) @@ -1169,9 +1226,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("You cannot combine --no-ff with --ff-only.")); if (!abort_current_merge) { - if (!argc && default_to_upstream) - argc = setup_with_upstream(&argv); - else if (argc == 1 && !strcmp(argv[0], "-")) + if (!argc) { + if (default_to_upstream) + argc = setup_with_upstream(&argv); + else + die(_("No commit specified and merge.defaultToUpstream not set.")); + } else if (argc == 1 && !strcmp(argv[0], "-")) argv[0] = "@{-1}"; } if (!argc) @@ -1194,7 +1254,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) argv += 2; argc -= 2; } else if (!head_commit) { - struct object *remote_head; + struct commit *remote_head; /* * If the merged head is a valid one there is no reason * to forbid "git merge" into a branch yet to be born. @@ -1208,13 +1268,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (!allow_fast_forward) die(_("Non-fast-forward commit does not make sense into " "an empty head")); - remote_head = want_commit(argv[0]); + remote_head = get_merge_parent(argv[0]); if (!remote_head) die(_("%s - not something we can merge"), argv[0]); - read_empty(remote_head->sha1, 0); - update_ref("initial pull", "HEAD", remote_head->sha1, NULL, 0, - DIE_ON_ERR); - return 0; + read_empty(remote_head->object.sha1, 0); + update_ref("initial pull", "HEAD", remote_head->object.sha1, + NULL, 0, DIE_ON_ERR); + goto done; } else { struct strbuf merge_names = STRBUF_INIT; @@ -1222,19 +1282,20 @@ int cmd_merge(int argc, const char **argv, const char *prefix) head_arg = "HEAD"; /* - * All the rest are the commits being merged; - * prepare the standard merge summary message to - * be appended to the given message. If remote - * is invalid we will die later in the common - * codepath so we discard the error in this - * loop. + * All the rest are the commits being merged; prepare + * the standard merge summary message to be appended + * to the given message. */ for (i = 0; i < argc; i++) merge_name(argv[i], &merge_names); if (!have_message || shortlog_len) { - fmt_merge_msg(&merge_names, &merge_msg, !have_message, - shortlog_len); + struct fmt_merge_msg_opts opts; + memset(&opts, 0, sizeof(opts)); + opts.add_title = !have_message; + opts.shortlog_len = shortlog_len; + + fmt_merge_msg(&merge_names, &merge_msg, &opts); if (merge_msg.len) strbuf_setlen(&merge_msg, merge_msg.len - 1); } @@ -1251,21 +1312,27 @@ int cmd_merge(int argc, const char **argv, const char *prefix) strbuf_reset(&buf); for (i = 0; i < argc; i++) { - struct object *o; - struct commit *commit; - - o = want_commit(argv[i]); - if (!o) + struct commit *commit = get_merge_parent(argv[i]); + if (!commit) die(_("%s - not something we can merge"), argv[i]); - commit = lookup_commit(o->sha1); - commit->util = (void *)argv[i]; remotes = &commit_list_insert(commit, remotes)->next; - - strbuf_addf(&buf, "GITHEAD_%s", sha1_to_hex(o->sha1)); + strbuf_addf(&buf, "GITHEAD_%s", + sha1_to_hex(commit->object.sha1)); setenv(buf.buf, argv[i], 1); strbuf_reset(&buf); + if (!fast_forward_only && + merge_remote_util(commit) && + merge_remote_util(commit)->obj && + merge_remote_util(commit)->obj->type == OBJ_TAG) { + if (option_edit < 0) + option_edit = default_edit_option(); + allow_fast_forward = 0; + } } + if (option_edit < 0) + option_edit = 0; + if (!use_strategies) { if (!remoteheads->next) add_strategies(pull_twohead, DEFAULT_TWOHEAD); @@ -1301,13 +1368,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * but first the most common case of merging one remote. */ finish_up_to_date("Already up-to-date."); - return 0; + goto done; } else if (allow_fast_forward && !remoteheads->next && !common->next && !hashcmp(common->item->object.sha1, head_commit->object.sha1)) { /* Again the most common case of merging one remote. */ struct strbuf msg = STRBUF_INIT; - struct object *o; + struct commit *commit; char hex[41]; strcpy(hex, find_unique_abbrev(head_commit->object.sha1, DEFAULT_ABBREV)); @@ -1321,16 +1388,21 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (have_message) strbuf_addstr(&msg, " (no commit created; -m option ignored)"); - o = want_commit(sha1_to_hex(remoteheads->item->object.sha1)); - if (!o) - return 1; + commit = remoteheads->item; + if (!commit) { + ret = 1; + goto done; + } - if (checkout_fast_forward(head_commit->object.sha1, remoteheads->item->object.sha1)) - return 1; + if (checkout_fast_forward(head_commit->object.sha1, + commit->object.sha1)) { + ret = 1; + goto done; + } - finish(head_commit, o->sha1, msg.buf); + finish(head_commit, commit->object.sha1, msg.buf); drop_save(); - return 0; + goto done; } else if (!remoteheads->next && common->next) ; /* @@ -1348,8 +1420,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) git_committer_info(IDENT_ERROR_ON_NO_NAME); printf(_("Trying really trivial in-index merge...\n")); if (!read_tree_trivial(common->item->object.sha1, - head_commit->object.sha1, remoteheads->item->object.sha1)) - return merge_trivial(head_commit); + head_commit->object.sha1, + remoteheads->item->object.sha1)) { + ret = merge_trivial(head_commit); + goto done; + } printf(_("Nope.\n")); } } else { @@ -1377,7 +1452,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } if (up_to_date) { finish_up_to_date("Already up-to-date. Yeeah!"); - return 0; + goto done; } } @@ -1459,9 +1534,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * If we have a resulting tree, that means the strategy module * auto resolved the merge cleanly. */ - if (automerge_was_ok) - return finish_automerge(head_commit, common, result_tree, - wt_strategy); + if (automerge_was_ok) { + ret = finish_automerge(head_commit, common, result_tree, + wt_strategy); + goto done; + } /* * Pick the result from the best strategy and have the user fix @@ -1475,7 +1552,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); - return 2; + ret = 2; + goto done; } else if (best_strategy == wt_strategy) ; /* We already have its result in the working tree. */ else { @@ -1491,10 +1569,13 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else write_merge_state(); - if (merge_was_ok) { + if (merge_was_ok) fprintf(stderr, _("Automatic merge went well; " "stopped before committing as requested\n")); - return 0; - } else - return suggest_conflicts(option_renormalize); + else + ret = suggest_conflicts(option_renormalize); + +done: + free(branch_to_free); + return ret; } diff --git a/builtin/notes.c b/builtin/notes.c index f8e437db0..3644d140e 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -301,12 +301,12 @@ void commit_notes(struct notes_tree *t, const char *msg) return; /* don't have to commit an unchanged tree */ /* Prepare commit message and reflog message */ - strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */ strbuf_addstr(&buf, msg); if (buf.buf[buf.len - 1] != '\n') strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */ - create_notes_commit(t, NULL, buf.buf + 7, commit_sha1); + create_notes_commit(t, NULL, &buf, commit_sha1); + strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */ update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR); strbuf_release(&buf); @@ -804,6 +804,8 @@ static int merge_commit(struct notes_merge_options *o) struct notes_tree *t; struct commit *partial; struct pretty_print_context pretty_ctx; + void *local_ref_to_free; + int ret; /* * Read partial merge result from .git/NOTES_MERGE_PARTIAL, @@ -825,7 +827,8 @@ static int merge_commit(struct notes_merge_options *o) t = xcalloc(1, sizeof(struct notes_tree)); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); - o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, NULL); + o->local_ref = local_ref_to_free = + resolve_refdup("NOTES_MERGE_REF", sha1, 0, NULL); if (!o->local_ref) die("Failed to resolve NOTES_MERGE_REF"); @@ -843,7 +846,9 @@ static int merge_commit(struct notes_merge_options *o) free_notes(t); strbuf_release(&msg); - return merge_abort(o); + ret = merge_abort(o); + free(local_ref_to_free); + return ret; } static int merge(int argc, const char **argv, const char *prefix) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index ef703dfeb..0f2e7b8f5 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -76,7 +76,7 @@ static struct pack_idx_option pack_idx_opts; static const char *base_name; static int progress = 1; static int window = 10; -static unsigned long pack_size_limit, pack_size_limit_cfg; +static unsigned long pack_size_limit; static int depth = 50; static int delta_search_threads; static int pack_to_stdout; @@ -638,7 +638,6 @@ static void write_pack_file(void) uint32_t i = 0, j; struct sha1file *f; off_t offset; - struct pack_header hdr; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; struct object_entry **write_order; @@ -652,22 +651,14 @@ static void write_pack_file(void) unsigned char sha1[20]; char *pack_tmp_name = NULL; - if (pack_to_stdout) { + if (pack_to_stdout) f = sha1fd_throughput(1, "", progress_state); - } else { - char tmpname[PATH_MAX]; - int fd; - fd = odb_mkstemp(tmpname, sizeof(tmpname), - "pack/tmp_pack_XXXXXX"); - pack_tmp_name = xstrdup(tmpname); - f = sha1fd(fd, pack_tmp_name); - } - - hdr.hdr_signature = htonl(PACK_SIGNATURE); - hdr.hdr_version = htonl(PACK_VERSION); - hdr.hdr_entries = htonl(nr_remaining); - sha1write(f, &hdr, sizeof(hdr)); - offset = sizeof(hdr); + else + f = create_tmp_packfile(&pack_tmp_name); + + offset = write_pack_header(f, nr_remaining); + if (!offset) + die_errno("unable to write pack header"); nr_written = 0; for (; i < nr_objects; i++) { struct object_entry *e = write_order[i]; @@ -693,20 +684,8 @@ static void write_pack_file(void) if (!pack_to_stdout) { struct stat st; - const char *idx_tmp_name; char tmpname[PATH_MAX]; - idx_tmp_name = write_idx_file(NULL, written_list, nr_written, - &pack_idx_opts, sha1); - - snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", - base_name, sha1_to_hex(sha1)); - free_pack_by_name(tmpname); - if (adjust_shared_perm(pack_tmp_name)) - die_errno("unable to make temporary pack file readable"); - if (rename(pack_tmp_name, tmpname)) - die_errno("unable to rename temporary pack file"); - /* * Packs are runtime accessed in their mtime * order since newer packs are more likely to contain @@ -714,28 +693,27 @@ static void write_pack_file(void) * packs then we should modify the mtime of later ones * to preserve this property. */ - if (stat(tmpname, &st) < 0) { + if (stat(pack_tmp_name, &st) < 0) { warning("failed to stat %s: %s", - tmpname, strerror(errno)); + pack_tmp_name, strerror(errno)); } else if (!last_mtime) { last_mtime = st.st_mtime; } else { struct utimbuf utb; utb.actime = st.st_atime; utb.modtime = --last_mtime; - if (utime(tmpname, &utb) < 0) + if (utime(pack_tmp_name, &utb) < 0) warning("failed utime() on %s: %s", tmpname, strerror(errno)); } - snprintf(tmpname, sizeof(tmpname), "%s-%s.idx", - base_name, sha1_to_hex(sha1)); - if (adjust_shared_perm(idx_tmp_name)) - die_errno("unable to make temporary index file readable"); - if (rename(idx_tmp_name, tmpname)) - die_errno("unable to rename temporary index file"); - - free((void *) idx_tmp_name); + /* Enough space for "-.pack"? */ + if (sizeof(tmpname) <= strlen(base_name) + 50) + die("pack base name '%s' too long", base_name); + snprintf(tmpname, sizeof(tmpname), "%s-", base_name); + finish_tmp_packfile(tmpname, pack_tmp_name, + written_list, nr_written, + &pack_idx_opts, sha1); free(pack_tmp_name); puts(sha1_to_hex(sha1)); } @@ -2103,10 +2081,6 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } - if (!strcmp(k, "pack.packsizelimit")) { - pack_size_limit_cfg = git_config_ulong(k, v); - return 0; - } return git_default_config(k, v, cb); } diff --git a/builtin/prune.c b/builtin/prune.c index e65690ba3..58d7cb832 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -5,6 +5,7 @@ #include "builtin.h" #include "reachable.h" #include "parse-options.h" +#include "progress.h" #include "dir.h" static const char * const prune_usage[] = { @@ -14,6 +15,7 @@ static const char * const prune_usage[] = { static int show_only; static int verbose; static unsigned long expire; +static int show_progress = -1; static int prune_tmp_object(const char *path, const char *filename) { @@ -124,9 +126,11 @@ static void remove_temporary_files(const char *path) int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; + struct progress *progress = NULL; const struct option options[] = { OPT__DRY_RUN(&show_only, "do not remove, show only"), OPT__VERBOSE(&verbose, "report pruned objects"), + OPT_BOOL(0, "progress", &show_progress, "show progress"), OPT_DATE(0, "expire", &expire, "expire objects older than