diff options
85 files changed, 2714 insertions, 472 deletions
@@ -104,6 +104,7 @@ git-receive-pack git-reflog git-relink git-remote +git-remote-curl git-repack git-replace git-repo-config diff --git a/Documentation/RelNotes-1.6.4.3.txt b/Documentation/RelNotes-1.6.4.3.txt new file mode 100644 index 0000000..4f29bab --- /dev/null +++ b/Documentation/RelNotes-1.6.4.3.txt @@ -0,0 +1,29 @@ +GIT v1.6.4.3 Release Notes +========================== + +Fixes since v1.6.4.2 +-------------------- + +* "git clone" from an empty repository gave unnecessary error message, + even though it did everything else correctly. + +* "git cvsserver" invoked git commands via "git-foo" style, which has long + been deprecated. + +* "git fetch" and "git clone" had an extra sanity check to verify the + presense of the corresponding *.pack file before downloading *.idx + file by issuing a HEAD request. Github server however sometimes + gave 500 (Internal server error) response to HEAD even if a GET + request for *.pack file to the same URL would have succeeded, and broke + clone over HTTP from some of their repositories. As a workaround, this + verification has been removed (as it is not absolutely necessary). + +* "git grep" did not like relative pathname to refer outside the current + directory when run from a subdirectory. + +* an error message from "git push" was formatted in a very ugly way. + +* "git svn" did not quote the subversion user name correctly when + running its author-prog helper program. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.txt b/Documentation/RelNotes-1.6.5.txt index 84a8451..e560af1 100644 --- a/Documentation/RelNotes-1.6.5.txt +++ b/Documentation/RelNotes-1.6.5.txt @@ -1,8 +1,9 @@ GIT v1.6.5 Release Notes ======================== -In git 1.7.0, which is planned to be the release after 1.6.5, "git push" -into a branch that is currently checked out will be refused by default. +In git 1.7.0, which was planned to be the release after 1.6.5, "git +push" into a branch that is currently checked out will be refused by +default. You can choose what should happen upon such a push by setting the configuration variable receive.denyCurrentBranch in the receiving @@ -44,8 +45,14 @@ Updates since v1.6.4 outperforms the default fallback implementation we borrowed from Mozzilla. + * Unnecessary inefficiency in deepening of a shallow repository has + been removed. + (usability, bells and whistles) + * Human writable date format to various options, e.g. --since=yesterday, + master@{2000.09.17}, are taught to infer some omitted input properly. + * refs/replace/ hierarchy is designed to be usable as a replacement of the "grafts" mechanism, with the added advantage that it can be transferred across repositories. @@ -54,10 +61,23 @@ Updates since v1.6.4 * "git am" handles input e-mail files that has CRLF line endings sensibly. + * "git am" learned "--scissors" option to allow you to discard early part + of an incoming e-mail. + + * "git checkout", "git reset" and "git stash" learned to pick and + choose to use selected changes you made, similar to "git add -p". + + * "git clone" learned a "-b" option to pick a HEAD to check out + different from the remote's default branch. + * "git commit --dry-run $args" is a new recommended way to ask "what would happen if I try to commit with these arguments." - * "git cvsimport" now supports password-protected pserver access. + * "git commit --dry-run" and "git status" shows conflicted paths in a + separate section to make them easier to spot during a merge. + + * "git cvsimport" now supports password-protected pserver access even + when the password is not taken from ~/.cvspass file. * "git fast-export" learned --no-data option that can be useful when reordering commits and trees without touching the contents of @@ -76,8 +96,6 @@ Updates since v1.6.4 * informational output from "git reset" that lists the locally modified paths is made consistent with that of "git checkout $another_branch". - * "git status" gives more descriptive output for unmerged paths. - * "git submodule" learned to give submodule name to scripts run with "foreach" subcommand. @@ -87,6 +105,9 @@ Updates since v1.6.4 tree vs the commit bound at submodule path, instead of comparing the index. + * "git upload-pack", which is the server side support for "git clone" and + "git fetch", can call a new post-upload-pack hook for statistics purposes. + (developers) * With GIT_TEST_OPTS="--root=/p/a/t/h", tests can be run outside the @@ -104,6 +125,6 @@ Fixes since v1.6.4 -- exec >/var/tmp/1 -O=v1.6.4.1-266-g235db15 +O=v1.6.4.2-298-gdf01e7c echo O=$(git describe master) git shortlog --no-merges $O..master --not maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 5256c7f..be0b8ca 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -113,6 +113,21 @@ For command-specific variables, you will find a more detailed description in the appropriate manual page. You will find a description of non-core porcelain configuration variables in the respective porcelain documentation. +advice.*:: + When set to 'true', display the given optional help message. + When set to 'false', do not display. The configuration variables + are: ++ +-- + pushNonFastForward:: + Advice shown when linkgit:git-push[1] refuses + non-fast-forward refs. Default: true. + statusHints:: + Directions on how to stage/unstage/add shown in the + output of linkgit:git-status[1] and the template shown + when writing commit messages. Default: true. +-- + core.fileMode:: If false, the executable bit differences between the index and the working copy are ignored; useful on broken filesystems like FAT. @@ -1500,6 +1515,19 @@ url.<base>.insteadOf:: never-before-seen repository on the site. When more than one insteadOf strings match a given URL, the longest match is used. +url.<base>.pushInsteadOf:: + Any URL that starts with this value will not be pushed to; + instead, it will be rewritten to start with <base>, and the + resulting URL will be pushed to. In cases where some site serves + a large number of repositories, and serves them with multiple + access methods, some of which do not allow push, this feature + allows people to specify a pull-only URL and have git + automatically use an appropriate URL to push, even for a + never-before-seen repository on the site. When more than one + pushInsteadOf strings match a given URL, the longest match is + used. If a remote has an explicit pushurl, git will ignore this + setting for that remote. + user.email:: Your email address to be recorded in any newly created commits. Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index ea3b1bc..5eb2b0e 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,3 +1,4 @@ +ifndef::git-pull[] -q:: --quiet:: Pass --quiet to git-fetch-pack and silence any other internally @@ -6,6 +7,7 @@ -v:: --verbose:: Be verbose. +endif::git-pull[] -a:: --append:: diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index fcacc94..67ad5da 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -13,7 +13,7 @@ SYNOPSIS [--3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] - [--reject] [-q | --quiet] + [--reject] [-q | --quiet] [--scissors | --no-scissors] [<mbox> | <Maildir>...] 'git am' (--skip | --resolved | --abort) @@ -39,6 +39,14 @@ OPTIONS --keep:: Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]). +-c:: +--scissors:: + Remove everything in body before a scissors line (see + linkgit:git-mailinfo[1]). + +---no-scissors:: + Ignore scissors lines (see linkgit:git-mailinfo[1]). + -q:: --quiet:: Be quiet. Only print error messages. @@ -128,10 +136,8 @@ the commit, after stripping common prefix "[PATCH <anything>]". The "Subject: " line is supposed to concisely describe what the commit is about in one line of text. -"From: " and "Subject: " lines starting the body (the rest of the -message after the blank line terminating the RFC2822 headers) -override the respective commit author name and title values taken -from the headers. +"From: " and "Subject: " lines starting the body override the respective +commit author name and title values taken from the headers. The commit message is formed by the title taken from the "Subject: ", a blank line and the body of the message up to diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index b1314b5..37c1810 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git checkout' [-q] [-f] [-m] [<branch>] 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... +'git checkout' --patch [<tree-ish>] [--] [<paths>...] DESCRIPTION ----------- @@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git branch`. As a convenience, --track without `-b` implies branch creation; see the description of --track below. -When <paths> are given, this command does *not* switch +When <paths> or --patch are given, this command does *not* switch branches. It updates the named paths in the working tree from the index file, or from a named <tree-ish> (most often a commit). In this case, the `-b` and `--track` options are meaningless and giving @@ -115,6 +116,16 @@ the conflicted merge in the specified paths. "merge" (default) and "diff3" (in addition to what is shown by "merge" style, shows the original contents). +-p:: +--patch:: + Interactively select hunks in the difference between the + <tree-ish> (or the index, if unspecified) and the working + tree. The chosen hunks are then applied in reverse to the + working tree (and if a <tree-ish> was specified, the index). ++ +This means that you can use `git checkout -p` to selectively discard +edits from your current working tree. + <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 88ea272..f23100e 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -84,7 +84,7 @@ its source repository, you can simply run `git repack -a` to copy all objects from the source repository into a pack in the cloned repository. --reference <repository>:: - If the reference repository is on the local machine + If the reference repository is on the local machine, automatically setup .git/objects/info/alternates to obtain objects from the reference repository. Using an already existing repository as an alternate will @@ -127,6 +127,13 @@ objects from the source repository into a pack in the cloned repository. Instead of using the remote name 'origin' to keep track of the upstream repository, use <name>. +--branch <name>:: +-b <name>:: + Instead of pointing the newly created HEAD to the branch pointed + to by the cloned repositoroy's HEAD, point to <name> branch + instead. In a non-bare repository, this is the branch that will + be checked out. + --upload-pack <upload-pack>:: -u <upload-pack>:: When given, and the repository to clone from is accessed diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 8d95aaa..996c3fc 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message SYNOPSIS -------- -'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] <msg> <patch> +'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch> DESCRIPTION @@ -49,6 +49,25 @@ conversion, even with this flag. -n:: Disable all charset re-coding of the metadata. +--scissors:: + Remove everything in body before a scissors line. A line that + mainly consists of scissors (either ">8" or "8<") and perforation + (dash "-") marks is called a scissors line, and is used to request + the reader to cut the message at that line. If such a line + appears in the body of the message before the patch, everything + before it (including the scissors line itself) is ignored when + this option is used. ++ +This is useful if you want to begin your message in a discussion thread +with comments and suggestions on the message you are responding to, and to +conclude it with a patch submission, separating the discussion and the +beginning of the proposed commit log message with a scissors line. ++ +This can enabled by default with the configuration option mailinfo.scissors. + +--no-scissors:: + Ignore scissors lines. Useful for overriding mailinfo.scissors settings. + <msg>:: The commit log message extracted from e-mail, usually except the title line which comes from e-mail Subject. diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index d4037de..579e8d2 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -9,7 +9,7 @@ git-quiltimport - Applies a quilt patchset onto the current branch SYNOPSIS -------- [verse] -'git quiltimport' [--dry-run] [--author <author>] [--patches <dir>] +'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>] DESCRIPTION diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt new file mode 100644 index 0000000..173ee23 --- /dev/null +++ b/Documentation/git-remote-helpers.txt @@ -0,0 +1,71 @@ +git-remote-helpers(1) +===================== + +NAME +---- +git-remote-helpers - Helper programs for interoperation with remote git + +SYNOPSIS +-------- +'git remote-<transport>' <remote> + +DESCRIPTION +----------- + +These programs are normally not used directly by end users, but are +invoked by various git programs that interact with remote repositories +when the repository they would operate on will be accessed using +transport code not linked into the main git binary. Various particular +helper programs will behave as documented here. + +COMMANDS +-------- + +Commands are given by the caller on the helper's standard input, one per line. + +'capabilities':: + Lists the capabilities of the helper, one per line, ending + with a blank line. + +'list':: + Lists the refs, one per line, in the format "<value> <name> + [<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for + a symref, or "?" to indicate that the helper could not get the + value of the ref. A space-separated list of attributes follows + the name; unrecognized attributes are ignored. After the + complete list, outputs a blank line. + +'fetch' <sha1> <name>:: + Fetches the given object, writing the necessary objects to the + database. Outputs a blank line when the fetch is + complete. Only objects which were reported in the ref list + with a sha1 may be fetched this way. ++ +Supported if the helper has the "fetch" capability. + +If a fatal error occurs, the program writes the error message to +stderr and exits. The caller should expect that a suitable error +message has been printed if the child closes the connection without +completing a valid response for the current command. + +Additional commands may be supported, as may be determined from +capabilities reported by the helper. + +CAPABILITIES +------------ + +'fetch':: + This helper supports the 'fetch' command. + +REF LIST ATTRIBUTES +------------------- + +None are defined yet, but the caller must accept any which are supplied. + +Documentation +------------- +Documentation by Daniel Barkalow. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index abb25d1..469cf6d 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>] 'git reset' [-q] [<commit>] [--] <paths>... +'git reset' --patch [<commit>] [--] [<paths>...] DESCRIPTION ----------- @@ -23,8 +24,9 @@ the undo in the history. If you want to undo a commit other than the latest on a branch, linkgit:git-revert[1] is your friend. -The second form with 'paths' is used to revert selected paths in -the index from a given commit, without moving HEAD. +The second and third forms with 'paths' and/or --patch are used to +revert selected paths in the index from a given commit, without moving +HEAD. OPTIONS @@ -50,6 +52,15 @@ OPTIONS and updates the files that are different between the named commit and the current commit in the working tree. +-p:: +--patch:: + Interactively select hunks in the difference between the index + and <commit> (defaults to HEAD). The chosen hunks are applied + in reverse to the index. ++ +This means that `git reset -p` is the opposite of `git add -p` (see +linkgit:git-add[1]). + -q:: Be quiet, only report errors. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 2f5ca7b..3f14b72 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,7 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [--keep-index] [-q|--quiet] [<message>]] +'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] 'git stash' clear 'git stash' create @@ -42,15 +42,27 @@ is also possible). OPTIONS ------- -save [--keep-index] [-q|--quiet] [<message>]:: +save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: Save your local modifications to a new 'stash', and run `git reset - --hard` to revert them. This is the default action when no - subcommand is given. The <message> part is optional and gives - the description along with the stashed state. + --hard` to revert them. The <message> part is optional and gives + the description along with the stashed state. For quickly making + a snapshot, you can omit _both_ "save" and <message>, but giving + only <message> does not trigger this action to prevent a misspelled + subcommand from making an unwanted stash. + If the `--keep-index` option is used, all changes already added to the index are left intact. ++ +With `--patch`, you can interactively select hunks from in the diff +between HEAD and the working tree to be stashed. The stash entry is +constructed such that its index state is the same as the index state +of your repository, and its worktree contains only the changes you +selected interactively. The selected changes are then rolled back +from your worktree. ++ +The `--patch` option implies `--keep-index`. You can use +`--no-keep-index` to override this. list [<options>]:: diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index b8e49dc..63f3b5c 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -20,6 +20,8 @@ The UI for the protocol is on the 'git-fetch-pack' side, and the program pair is meant to be used to pull updates from a remote repository. For push operations, see 'git-send-pack'. +After finishing the operation successfully, `post-upload-pack` +hook is called (see linkgit:githooks[5]). OPTIONS ------- diff --git a/Documentation/git.txt b/Documentation/git.txt index ad44cac..f91cabb 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.4.2/git.html[documentation for release 1.6.4.2] +* link:v1.6.4.3/git.html[documentation for release 1.6.4.3] * release notes for + link:RelNotes-1.6.4.3.txt[1.6.4.3], link:RelNotes-1.6.4.2.txt[1.6.4.2], link:RelNotes-1.6.4.1.txt[1.6.4.1], link:RelNotes-1.6.4.txt[1.6.4]. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 1c73673..79f633e 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -26,8 +26,11 @@ executable by default. This document describes the currently defined hooks. +HOOKS +----- + applypatch-msg --------------- +~~~~~~~~~~~~~~ This hook is invoked by 'git-am' script. It takes a single parameter, the name of the file that holds the proposed commit @@ -43,7 +46,7 @@ The default 'applypatch-msg' hook, when enabled, runs the 'commit-msg' hook, if the latter is enabled. pre-applypatch --------------- +~~~~~~~~~~~~~~ This hook is invoked by 'git-am'. It takes no parameter, and is invoked after the patch is applied, but before a commit is made. @@ -58,7 +61,7 @@ The default 'pre-applypatch' hook, when enabled, runs the 'pre-commit' hook, if the latter is enabled. post-applypatch ---------------- +~~~~~~~~~~~~~~~ This hook is invoked by 'git-am'. It takes no parameter, and is invoked after the patch is applied and a commit is made. @@ -67,7 +70,7 @@ This hook is meant primarily for notification, and cannot affect the outcome of 'git-am'. pre-commit ----------- +~~~~~~~~~~ This hook is invoked by 'git-commit', and can be bypassed with `\--no-verify` option. It takes no parameter, and is @@ -84,7 +87,7 @@ variable `GIT_EDITOR=:` if the command will not bring up an editor to modify the commit message. prepare-commit-msg ------------------- +~~~~~~~~~~~~~~~~~~ This hook is invoked by 'git-commit' right after preparing the default log message, and before the editor is started. @@ -109,7 +112,7 @@ The sample `prepare-commit-msg` hook that comes with git comments out the `Conflicts:` part of a merge's commit message. commit-msg ----------- +~~~~~~~~~~ This hook is invoked by 'git-commit', and can be bypassed with `\--no-verify` option. It takes a single parameter, the @@ -126,7 +129,7 @@ The default 'commit-msg' hook, when enabled, detects duplicate "Signed-off-by" lines, and aborts the commit if one is found. post-commit ------------ +~~~~~~~~~~~ This hook is invoked by 'git-commit'. It takes no parameter, and is invoked after a commit is made. @@ -135,14 +138,14 @@ This hook is meant primarily for notification, and cannot affect the outcome of 'git-commit'. pre-rebase ----------- +~~~~~~~~~~ This hook is called by 'git-rebase' and can be used to prevent a branch from getting rebased. post-checkout ------------ +~~~~~~~~~~~~~ This hook is invoked when a 'git-checkout' is run after having updated the worktree. The hook is given three parameters: the ref of the previous HEAD, @@ -160,7 +163,7 @@ differences from the previous HEAD if different, or set working dir metadata properties. post-merge ------------ +~~~~~~~~~~ This hook is invoked by 'git-merge', which happens when a 'git-pull' is done on a local repository. The hook takes a single parameter, a status @@ -175,7 +178,7 @@ for an example of how to do this. [[pre-receive]] pre-receive ------------ +~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -204,7 +207,7 @@ for the user. [[update]] update ------- +~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -247,7 +250,7 @@ unannotated tags to be pushed. [[post-receive]] post-receive ------------- +~~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -277,7 +280,7 @@ emails. [[post-update]] post-update ------------ +~~~~~~~~~~~ This hook is invoked by 'git-receive-pack' on the remote repository, which happens when a 'git-push' is done on a local repository. @@ -307,8 +310,37 @@ Both standard output and standard error output are forwarded to 'git-send-pack' on the other end, so you can simply `echo` messages for the user. +post-upload-pack +---------------- + +After upload-pack successfully finishes its operation, this hook is called +for logging purposes. + +The hook is passed various pieces of information, one per line, from its +standard input. Currently the following items can be fed to the hook, but +more types of information may be added in the future: + +want SHA-1:: + 40-byte hexadecimal object name the client asked to include in the + resulting pack. Can occur one or more times in the input. + +have SHA-1:: + 40-byte hexadecimal object name the client asked to exclude from + the resulting pack, claiming to have them already. Can occur zero + or more times in the input. + +time float:: + Number of seconds spent for creating the packfile. + +size decimal:: + Size of the resulting packfile in bytes. + +kind string: + Either "clone" (when the client did not give us any "have", and asked + for all our refs with "want"), or "fetch" (otherwise). + pre-auto-gc ------------ +~~~~~~~~~~~ This hook is invoked by 'git-gc --auto'. It takes no parameter, and exiting with non-zero status from this script causes the 'git-gc --auto' diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 5355ebc..d813ceb 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -67,3 +67,21 @@ For example, with this: a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be rewritten in any context that takes a URL to be "git://git.host.xz/repo.git". +If you want to rewrite URLs for push only, you can create a +configuration section of the form: + +------------ + [url "<actual url base>"] + pushInsteadOf = <other url base> +------------ + +For example, with this: + +------------ + [url "ssh://example.org/"] + pushInsteadOf = git://example.org/ +------------ + +a URL like "git://example.org/path/to/repo.git" will be rewritten to +"ssh://example.org/path/to/repo.git" for pushes, but pulls will still +use the original URL. @@ -13,6 +13,10 @@ that uses $prefix, the built results have some paths encoded, which are derived from $prefix, so "make all; make prefix=/usr install" would not work. +The beginning of the Makefile documents many variables that affect the way +git is built. You can override them either from the command line, or in a +config.mak file. + Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead @@ -48,32 +52,42 @@ Issues of note: export GIT_EXEC_PATH PATH GITPERLLIB - Git is reasonably self-sufficient, but does depend on a few external - programs and libraries: + programs and libraries. Git can be used without most of them by adding + the approriate "NO_<LIBRARY>=YesPlease" to the make command line or + config.mak file. - "zlib", the compression library. Git won't build without it. - - "openssl". Unless you specify otherwise, you'll get the SHA1 - library from here. + - "ssh" is used to push and pull over the net. - If you don't have openssl, you can use one of the SHA1 libraries - that come with git (git includes the one from Mozilla, and has - its own PowerPC and ARM optimized ones too - see the Makefile). + - A POSIX-compliant shell is required to run many scripts needed + for everyday use (e.g. "bisect", "pull"). - - libcurl library; git-http-fetch and git-fetch use them. You - might also want the "curl" executable for debugging purposes. - If you do not use http transfer, you are probably OK if you - do not have them. + - "Perl" 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. - - expat library; git-http-push uses it for remote lock - management over DAV. Similar to "curl" above, this is optional. + - "openssl" library is used by git-imap-send to use IMAP over SSL. + If you don't need it, use NO_OPENSSL. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the - history graphically, and in git-gui. + By default, git uses OpenSSL for SHA1 but it will use it's own + library (inspired by Mozilla's) with either NO_OPENSSL or + BLK_SHA1. Also included is a version optimized for PowerPC + (PPC_SHA1). + + - "libcurl" library is used by git-http-fetch and git-fetch. You + might also want the "curl" executable for debugging purposes. + If you do not use http:// or https:// repositories, you do not + have to have them (use NO_CURL). - - "ssh" is used to push and pull over the net + - "expat" library; git-http-push uses it for remote lock + management over DAV. Similar to "curl" above, this is optional + (with NO_EXPAT). - - "perl" and POSIX-compliant shells are needed to use most of - the bare-bones Porcelainish scripts. + - "wish", the Tcl/Tk windowing shell is used in gitk to show the + history graphically, and in git-gui. If you don't want gitk or + git-gui, you can use NO_TCLTK. - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not @@ -91,7 +91,9 @@ all:: # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # -# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). +# Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin). +# +# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). # @@ -378,7 +380,8 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-whatchanged$X -# what 'all' will build and 'install' will install, in gitexecdir +# what 'all' will build and 'install' will install in gitexecdir, +# excluding programs for built-in commands ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir @@ -397,6 +400,7 @@ export PERL_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a +LIB_H += advice.h LIB_H += archive.h LIB_H += attr.h LIB_H += blob.h @@ -454,6 +458,7 @@ LIB_H += utf8.h LIB_H += wt-status.h LIB_OBJS += abspath.o +LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o LIB_OBJS += archive.o @@ -545,6 +550,7 @@ LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o LIB_OBJS += transport.o +LIB_OBJS += transport-helper.o LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o @@ -704,6 +710,7 @@ ifeq ($(uname_S),SCO_SV) TAR = gtar endif ifeq ($(uname_S),Darwin) + NEEDS_CRYPTO_WITH_SSL = YesPlease NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) @@ -970,9 +977,7 @@ else else CURL_LIBCURL = -lcurl endif - BUILTIN_OBJS += builtin-http-fetch.o - EXTLIBS += $(CURL_LIBCURL) - LIB_OBJS += http.o http-walker.o + PROGRAMS += git-remote-curl$X git-http-fetch$X curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT @@ -1007,6 +1012,9 @@ ifndef NO_OPENSSL else OPENSSL_LINK = endif + ifdef NEEDS_CRYPTO_WITH_SSL + OPENSSL_LINK += -lcrypto + endif else BASIC_CFLAGS += -DNO_OPENSSL BLK_SHA1 = 1 @@ -1243,6 +1251,7 @@ ifndef V QUIET_LINK = @echo ' ' LINK $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; + QUIET_LNCP = @echo ' ' LN/CP $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir @@ -1470,12 +1479,21 @@ git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) -http.o http-walker.o http-push.o transport.o: http.h +http.o http-walker.o http-push.o: http.h +http.o http-walker.o: $(LIB_H) + +git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(CURL_LIBCURL) git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) + $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) builtin-revert.o wt-status.o: wt-status.h diff --git a/advice.c b/advice.c new file mode 100644 index 0000000..ae4b1e8 --- /dev/null +++ b/advice.c @@ -0,0 +1,27 @@ +#include "cache.h" + +int advice_push_nonfastforward = 1; +int advice_status_hints = 1; + +static struct { + const char *name; + int *preference; +} advice_config[] = { + { "pushnonfastforward", &advice_push_nonfastforward }, + { "statushints", &advice_status_hints }, +}; + +int git_default_advice_config(const char *var, const char *value) +{ + const char *k = skip_prefix(var, "advice."); + int i; + + for (i = 0; i < ARRAY_SIZE(advice_config); i++) { + if (strcmp(k, advice_config[i].name)) + continue; + *advice_config[i].preference = git_config_bool(var, value); + return 0; + } + + return 0; +} diff --git a/advice.h b/advice.h new file mode 100644 index 0000000..e9df8e0 --- /dev/null +++ b/advice.h @@ -0,0 +1,9 @@ +#ifndef ADVICE_H +#define ADVICE_H + +extern int advice_push_nonfastforward; +extern int advice_status_hints; + +int git_default_advice_config(const char *var, const char *value); + +#endif /* ADVICE_H */ diff --git a/builtin-add.c b/builtin-add.c index 006fd08..cb6e590 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -131,27 +131,27 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p return pathspec; } -int interactive_add(int argc, const char **argv, const char *prefix) +int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec) { - int status, ac; + int status, ac, pc = 0; const char **args; - const char **pathspec = NULL; - if (argc) { - pathspec = validate_pathspec(argc, argv, prefix); - if (!pathspec) - return -1; - } + if (pathspec) + while (pathspec[pc]) + pc++; - args = xcalloc(sizeof(const char *), (argc + 4)); + args = xcalloc(sizeof(const char *), (pc + 5)); ac = 0; args[ac++] = "add--interactive"; - if (patch_interactive) - args[ac++] = "--patch"; + if (patch_mode) + args[ac++] = patch_mode; + if (revision) + args[ac++] = revision; args[ac++] = "--"; - if (argc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc); - ac += argc; + if (pc) { + memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); + ac += pc; } args[ac] = NULL; @@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix) return status; } +int interactive_add(int argc, const char **argv, const char *prefix) +{ + const char **pathspec = NULL; + + if (argc) { + pathspec = validate_pathspec(argc, argv, prefix); + if (!pathspec) + return -1; + } + + return run_add_interactive(NULL, + patch_interactive ? "--patch" : NULL, + pathspec); +} + static int edit_patch(int argc, const char **argv, const char *prefix) { char *file = xstrdup(git_path("ADD_EDIT.patch")); @@ -183,7 +198,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) die ("Could not open '%s' for writing.", file); - rev.diffopt.file = fdopen(out, "w"); + rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) die ("Could not write patch"); diff --git a/builtin-checkout.c b/builtin-checkout.c index 36e2116..d050c37 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -566,6 +566,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb) return git_xmerge_config(var, value, cb); } +static int interactive_checkout(const char *revision, const char **pathspec, + struct checkout_opts *opts) +{ + return run_add_interactive(revision, "--patch=checkout", pathspec); +} + + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; @@ -574,6 +581,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) struct branch_info new; struct tree *source_tree = NULL; char *conflict_style = NULL; + int patch_mode = 0; struct option options[] = { OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), @@ -588,6 +596,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END(), }; int has_dash_dash; @@ -602,6 +611,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + if (patch_mode && (opts.track > 0 || opts.new_branch + || opts.new_branch_log || opts.merge || opts.force)) + die ("--patch is incompatible with all other options"); + /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { const char *argv0 = argv[0]; @@ -708,6 +721,9 @@ no_reference: if (!pathspec) die("invalid path specification"); + if (patch_mode) + return interactive_checkout(new.name, pathspec, &opts); + /* Checkout paths */ if (opts.new_branch) { if (argc == 1) { @@ -723,6 +739,9 @@ no_reference: return checkout_paths(source_tree, pathspec, &opts); } + if (patch_mode) + return interactive_checkout(new.name, NULL, &opts); + if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; if (strbuf_check_branch_ref(&buf, opts.new_branch)) diff --git a/builtin-clone.c b/builtin-clone.c index 991a7ae..bab2d84 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -41,6 +41,7 @@ static int option_quiet, option_no_checkout, option_bare, option_mirror; static int option_local, option_no_hardlinks, option_shared, option_recursive; static char *option_template, *option_reference, *option_depth; static char *option_origin = NULL; +static char *option_branch = NULL; static char *option_upload_pack = "git-upload-pack"; static int option_verbose; @@ -67,6 +68,8 @@ static struct option builtin_clone_options[] = { "reference repository"), OPT_STRING('o', "origin", &option_origin, "branch", "use <branch> instead of 'origin' to track upstream"), + OPT_STRING('b', "branch", &option_branch, "branch", + "checkout <branch> instead of the remote's HEAD"), OPT_STRING('u', "upload-pack", &option_upload_pack, "path", "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", @@ -266,7 +269,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) die_errno("failed to create link '%s'", dest->buf); option_no_hardlinks = 1; } - if (copy_file(dest->buf, src->buf, 0666)) + if (copy_file_with_time(dest->buf, src->buf, 0666)) die_errno("failed to copy file to '%s'", dest->buf); } closedir(dir); @@ -353,7 +356,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; int dest_exists; - const struct ref *refs, *head_points_at, *remote_head, *mapped_refs; + const struct ref *refs, *remote_head, *mapped_refs; + const struct ref *remote_head_points_at; + const struct ref *our_head_points_at; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; @@ -525,11 +530,31 @@ int cmd_clone(int argc, const char **argv, const char *prefix) mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf); remote_head = find_ref_by_name(refs, "HEAD"); - head_points_at = guess_remote_head(remote_head, mapped_refs, 0); + remote_head_points_at = + guess_remote_head(remote_head, mapped_refs, 0); + + if (option_branch) { + struct strbuf head = STRBUF_INIT; + strbuf_addstr(&head, src_ref_prefix); + strbuf_addstr(&head, option_branch); + our_head_points_at = + find_ref_by_name(mapped_refs, head.buf); + strbuf_release(&head); + + if (!our_head_points_at) { + warning("Remote branch %s not found in " + "upstream %s, using HEAD instead", + option_branch, option_origin); + our_head_points_at = remote_head_points_at; + } + } + else + our_head_points_at = remote_head_points_at; } else { warning("You appear to have cloned an empty repository."); - head_points_at = NULL; + our_head_points_at = NULL; + remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; if (!option_bare) @@ -537,41 +562,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix) "refs/heads/master"); } - if (head_points_at) { - /* Local default branch link */ - create_symref("HEAD", head_points_at->name, NULL); + if (remote_head_points_at && !option_bare) { + struct strbuf head_ref = STRBUF_INIT; + strbuf_addstr(&head_ref, branch_top.buf); + strbuf_addstr(&head_ref, "HEAD"); + create_symref(head_ref.buf, + remote_head_points_at->peer_ref->name, + reflog_msg.buf); + } + if (our_head_points_at) { + /* Local default branch link */ + create_symref("HEAD", our_head_points_at->name, NULL); if (!option_bare) { - struct strbuf head_ref = STRBUF_INIT; - const char *head = head_points_at->name; - - if (!prefixcmp(head, "refs/heads/")) - head += 11; - - /* Set up the initial local branch */ - - /* Local branch initial value */ + const char *head = skip_prefix(our_head_points_at->name, + "refs/heads/"); update_ref(reflog_msg.buf, "HEAD", - head_points_at->old_sha1, + our_head_points_at->old_sha1, NULL, 0, DIE_ON_ERR); - - strbuf_addstr(&head_ref, branch_top.buf); - strbuf_addstr(&head_ref, "HEAD"); - - /* Remote branch link */ - create_symref(head_ref.buf, - head_points_at->peer_ref->name, - reflog_msg.buf); - install_branch_config(0, head, option_origin, - head_points_at->name); + our_head_points_at->name); } } else if (remote_head) { /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) + if (!option_bare) { update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1, NULL, REF_NODEREF, DIE_ON_ERR); + our_head_points_at = remote_head; + } } else { /* Nothing to checkout out */ if (!option_no_checkout) @@ -580,8 +599,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_no_checkout = 1; } - if (transport) + if (transport) { transport_unlock_pack(transport); + transport_disconnect(transport); + } if (!option_no_checkout) { struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); @@ -603,7 +624,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) opts.src_index = &the_index; opts.dst_index = &the_index; - tree = parse_tree_indirect(remote_head->old_sha1); + tree = parse_tree_indirect(our_head_points_at->old_sha1); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); unpack_trees(1, &t, &opts); diff --git a/builtin-fetch.c b/builtin-fetch.c index 817dd6b..cb48c57 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -454,7 +454,7 @@ static int quickfetch(struct ref *ref_map) for (ref = ref_map; ref; ref = ref->next) { if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_in_full(revlist.in, "\n", 1) < 0) { + write_str_in_full(revlist.in, "\n") < 0) { if (errno != EPIPE && errno != EINVAL) error("failed write to rev-list: %s", strerror(errno)); err = -1; diff --git a/builtin-grep.c b/builtin-grep.c index ad0e0a5..761799d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -13,6 +13,7 @@ #include "parse-options.h" #include "userdiff.h" #include "grep.h" +#include "quote.h" #ifndef NO_EXTERNAL_GREP #ifdef __unix__ @@ -157,8 +158,8 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char unsigned long size; char *data; enum object_type type; - char *to_free = NULL; int hit; + struct strbuf pathbuf = STRBUF_INIT; data = read_sha1_file(sha1, &type, &size); if (!data) { @@ -166,26 +167,13 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char return 0; } if (opt->relative && opt->prefix_length) { - static char name_buf[PATH_MAX]; - char *cp; - int name_len = strlen(name) - opt->prefix_length + 1; - - if (!tree_name_len) - name += opt->prefix_length; - else { - if (ARRAY_SIZE(name_buf) <= name_len) - cp = to_free = xmalloc(name_len); - else - cp = name_buf; - memcpy(cp, name, tree_name_len); - strcpy(cp + tree_name_len, - name + tree_name_len + opt->prefix_length); - name = cp; - } + quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix); + strbuf_insert(&pathbuf, 0, name, tree_name_len); + name = pathbuf.buf; } hit = grep_buffer(opt, name, data, size); + strbuf_release(&pathbuf); free(data); - free(to_free); return hit; } @@ -195,6 +183,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) int i; char *data; size_t sz; + struct strbuf buf = STRBUF_INIT; if (lstat(filename, &st) < 0) { err_ret: @@ -219,8 +208,9 @@ static int grep_file(struct grep_opt *opt, const char *filename) } close(i); if (opt->relative && opt->prefix_length) - filename += opt->prefix_length; + filename = quote_path_relative(filename, -1, &buf, opt->prefix); i = grep_buffer(opt, filename, data, sz); + strbuf_release(&buf); free(data); return i; } @@ -503,6 +493,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, hit = external_grep(opt, paths, cached); if (hit >= 0) return hit; + hit = 0; } #endif @@ -798,6 +789,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) }; memset(&opt, 0, sizeof(opt)); + opt.prefix = prefix; opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; opt.relative = 1; opt.pathname = 1; @@ -868,15 +860,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) { + if (i < argc) paths = get_pathspec(prefix, argv + i); - if (opt.prefix_length && opt.relative) { - /* Make sure we do not get outside of paths */ - for (i = 0; paths[i]; i++) - if (strncmp(prefix, paths[i], opt.prefix_length)) - die("git grep: cannot generate relative filenames containing '..'"); - } - } else if (prefix) { paths = xcalloc(2, sizeof(const char *)); paths[0] = prefix; diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index b0b5d8f..d498b1c 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -25,6 +25,7 @@ static enum { static struct strbuf charset = STRBUF_INIT; static int patch_lines; static struct strbuf **p_hdr_data, **s_hdr_data; +static int use_scissors; #define MAX_HDR_PARSED 10 #define MAX_BOUNDARIES 5 @@ -712,6 +713,56 @@ static inline int patchbreak(const struct strbuf *line) return 0; } +static int is_scissors_line(const struct strbuf *line) +{ + size_t i, len = line->len; + int scissors = 0, gap = 0; + int first_nonblank = -1; + int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; + const char *buf = line->buf; + + for (i = 0; i < len; i++) { + if (isspace(buf[i])) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = i; + if (first_nonblank < 0) + first_nonblank = i; + if (buf[i] == '-') { + in_perforation = 1; + perforation++; + continue; + } + if (i + 1 < len && + (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + i++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + visible = last_nonblank - first_nonblank + 1; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + static int handle_commit_msg(struct strbuf *line) { static int still_looking = 1; @@ -723,7 +774,8 @@ static int handle_commit_msg(struct strbuf *line) strbuf_ltrim(line); if (!line->len) return 0; - if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) + still_looking = check_header(line, s_hdr_data, 0); + if (still_looking) return 0; } @@ -731,6 +783,24 @@ static int handle_commit_msg(struct strbuf *line) if (metainfo_charset) convert_to_utf8(line, charset.buf); + if (use_scissors && is_scissors_line(line)) { + int i; + rewind(cmitmsg); + ftruncate(fileno(cmitmsg), 0); + still_looking = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (s_hdr_data[i]) + strbuf_release(s_hdr_data[i]); + s_hdr_data[i] = NULL; + } + return 0; + } + if (patchbreak(line)) { fclose(cmitmsg); cmitmsg = NULL; @@ -885,12 +955,9 @@ static void handle_info(void) fprintf(fout, "\n"); } -static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, - const char *msg, const char *patch) +static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) { int peek; - keep_subject = ks; - metainfo_charset = encoding; fin = in; fout = out; @@ -924,8 +991,20 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, return 0; } +static int git_mailinfo_config(const char *var, const char *value, void *unused) +{ + if (prefixcmp(var, "mailinfo.")) + return git_default_config(var, value, unused); + if (!strcmp(var, "mailinfo.scissors")) { + use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + static const char mailinfo_usage[] = - "git mailinfo [-k] [-u | --encoding=<encoding> | -n] msg patch <mail >info"; + "git mailinfo [-k] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { @@ -934,7 +1013,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) /* NEEDSWORK: might want to do the optional .git/ directory * discovery */ - git_config(git_default_config, NULL); + git_config(git_mailinfo_config, NULL); def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); metainfo_charset = def_charset; @@ -948,6 +1027,10 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) metainfo_charset = NULL; else if (!prefixcmp(argv[1], "--encoding=")) metainfo_charset = argv[1] + 11; + else if (!strcmp(argv[1], "--scissors")) + use_scissors = 1; + else if (!strcmp(argv[1], "--no-scissors")) + use_scissors = 0; else usage(mailinfo_usage); argc--; argv++; @@ -956,5 +1039,5 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) if (argc != 3) usage(mailinfo_usage); - return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]); + return !!mailinfo(stdin, stdout, argv[1], argv[2]); } diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index ee6ca0e..dfe5b15 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -64,7 +64,7 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd < 0) die_errno("cannot open output file '%s'", name); - output = fdopen(fd, "w"); + output = xfdopen(fd, "w"); /* Copy it out, while searching for a line that begins with * "From " and having something that looks like a date format. diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 1ff2ce6..7a390e1 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1008,6 +1008,33 @@ static void add_preferred_base(unsigned char *sha1) it->pcache.tree_size = size; } +static void cleanup_preferred_base(void) +{ + struct pbase_tree *it; + unsigned i; + + it = pbase_tree; + pbase_tree = NULL; + while (it) { + struct pbase_tree *this = it; + it = this->next; + free(this->pcache.tree_data); + free(this); + } + + for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) { + if (!pbase_tree_cache[i]) + continue; + free(pbase_tree_cache[i]->tree_data); + free(pbase_tree_cache[i]); + pbase_tree_cache[i] = NULL; + } + + free(done_pbase_paths); + done_pbase_paths = NULL; + done_pbase_paths_num = done_pbase_paths_alloc = 0; +} + static void check_object(struct object_entry *entry) { if (entry->in_pack) { @@ -2312,6 +2339,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) rp_av[rp_ac] = NULL; get_object_list(rp_ac, rp_av); } + cleanup_preferred_base(); if (include_tag && nr_result) for_each_ref(add_ref_tag, NULL); stop_progress(&progress_state); diff --git a/builtin-push.c b/builtin-push.c index 67f6d96..6eda372 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -157,10 +157,10 @@ static int do_push(const char *repo, int flags) continue; error("failed to push some refs to '%s'", url[i]); - if (nonfastforward) { - printf("To prevent you from losing history, non-fast-forward updates were rejected.\n" - "Merge the remote changes before pushing again.\n" - "See 'non-fast forward' section of 'git push --help' for details.\n"); + if (nonfastforward && advice_push_nonfastforward) { + printf("To prevent you from losing history, non-fast-forward updates were rejected\n" + "Merge the remote changes before pushing again. See the 'non-fast forward'\n" + "section of 'git push --help' for details.\n"); } errs++; } diff --git a/builtin-reflog.c b/builtin-reflog.c index 95198c5..e23b5ef 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -362,7 +362,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, } else if (cmd->updateref && (write_in_full(lock->lock_fd, sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_in_full(lock->lock_fd, "\n", 1) != 1 || + write_str_in_full(lock->lock_fd, "\n") != 1 || close_ref(lock) < 0)) { status |= error("Couldn't write %s", lock->lk->filename); diff --git a/builtin-reset.c b/builtin-reset.c index 0fc0b07..73e6022 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -143,6 +143,17 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } +static int interactive_reset(const char *revision, const char **argv, + const char *prefix) +{ + const char **pathspec = NULL; + + if (*argv) + pathspec = get_pathspec(prefix, argv); + + return run_add_interactive(revision, "--patch=reset", pathspec); +} + static int read_from_tree(const char *prefix, const char **argv, unsigned char *tree_sha1, int refresh_flags) { @@ -184,6 +195,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) int cmd_reset(int argc, const char **argv, const char *prefix) { int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; + int patch_mode = 0; const char *rev = "HEAD"; unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; @@ -199,6 +211,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", MERGE), OPT_BOOLEAN('q', NULL, &quiet, "disable showing new HEAD in hard reset and progress message"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -252,6 +265,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die("Could not parse object '%s'.", rev); hashcpy(sha1, commit->object.sha1); + if (patch_mode) { + if (reset_type != NONE) + die("--patch is incompatible with --{hard,mixed,soft}"); + return interactive_reset(rev, argv + i, prefix); + } + /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ @@ -234,7 +234,7 @@ int create_bundle(struct bundle_header *header, const char *path, rls.git_cmd = 1; if (start_command(&rls)) return -1; - rls_fout = fdopen(rls.out, "r"); + rls_fout = xfdopen(rls.out, "r"); while (fgets(buffer, sizeof(buffer), rls_fout)) { unsigned char sha1[20]; if (buffer[0] == '-') { @@ -4,6 +4,7 @@ #include "git-compat-util.h" #include "strbuf.h" #include "hash.h" +#include "advice.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@ -923,13 +924,19 @@ extern const char *git_mailmap_file; extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); extern int copy_file(const char *dst, const char *src, int mode); -extern ssize_t read_in_full(int fd, void *buf, size_t count); -extern ssize_t write_in_full(int fd, const void *buf, size_t count); +extern int copy_file_with_time(const char *dst, const char *src, int mode); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg); extern void fsync_or_die(int fd, const char *); +extern ssize_t read_in_full(int fd, void *buf, size_t count); +extern ssize_t write_in_full(int fd, const void *buf, size_t count); +static inline ssize_t write_str_in_full(int fd, const char *str) +{ + return write_in_full(fd, str, strlen(str)); +} + /* pager.c */ extern void setup_pager(void); extern const char *pager_program; @@ -212,7 +212,7 @@ int write_shallow_commits(int fd, int use_pack_protocol) else { if (write_in_full(fd, hex, 40) != 40) break; - if (write_in_full(fd, "\n", 1) != 1) + if (write_str_in_full(fd, "\n") != 1) break; } } @@ -140,6 +140,8 @@ int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(int argc, const char **argv, const char *prefix); +extern int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec); static inline int single_parent(struct commit *commit) { diff --git a/compat/mingw.c b/compat/mingw.c index bed4178..36ef8d3 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -824,7 +824,7 @@ void mingw_execvp(const char *cmd, char *const *argv) free_path_split(path); } -char **copy_environ() +static char **copy_environ(void) { char **env; int i = 0; @@ -861,7 +861,7 @@ static int lookup_env(char **env, const char *name, size_t nmln) /* * If name contains '=', then sets the variable, otherwise it unsets it */ -char **env_setenv(char **env, const char *name) +static char **env_setenv(char **env, const char *name) { char *eq = strchrnul(name, '='); int i = lookup_env(env, name, eq-name); @@ -886,6 +886,18 @@ char **env_setenv(char **env, const char *name) return env; } +/* + * Copies global environ and adjusts variables as specified by vars. + */ +char **make_augmented_environ(const char *const *vars) +{ + char **env = copy_environ(); + + while (*vars) + env = env_setenv(env, *vars++); + return env; +} + /* this is the first function to call into WS_32; initialize it */ #undef gethostbyname struct hostent *mingw_gethostbyname(const char *host) diff --git a/compat/mingw.h b/compat/mingw.h index 948de66..c43917c 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -222,9 +222,8 @@ void mingw_open_html(const char *path); * helpers */ -char **copy_environ(void); +char **make_augmented_environ(const char *const *vars); void free_environ(char **env); -char **env_setenv(char **env, const char *name); /* * A replacement of main() that ensures that argv[0] has a path @@ -627,6 +627,9 @@ int git_default_config(const char *var, const char *value, void *dummy) if (!prefixcmp(var, "mailmap.")) return git_default_mailmap_config(var, value); + if (!prefixcmp(var, "advice.")) + return git_default_advice_config(var, value); + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; @@ -1116,7 +1119,7 @@ int git_config_set_multivar(const char *key, const char *value, copy_end - copy_begin) goto write_err_out; if (new_line && - write_in_full(fd, "\n", 1) != 1) + write_str_in_full(fd, "\n") != 1) goto write_err_out; } copy_begin = store.offset[i]; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index bf688e1..9859204 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1532,7 +1532,7 @@ _git_config () url.*.*) local pfx="${cur%.*}." cur="${cur##*.}" - __gitcomp "insteadof" "$pfx" "$cur" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur" return ;; esac diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 8c70ad8..214930a 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1046,7 +1046,7 @@ The FILES list must be sorted." (defun git-add-file () "Add marked file(s) to the index cache." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored)))) + (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged)))) ;; FIXME: add support for directories (unless files (push (file-relative-name (read-file-name "File to add: " nil nil t)) files)) @@ -1119,15 +1119,6 @@ The FILES list must be sorted." (when buffer (with-current-buffer buffer (revert-buffer t t t))))) (git-success-message "Reverted" names)))))) -(defun git-resolve-file () - "Resolve conflicts in marked file(s)." - (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unmerged)))) - (when files - (when (apply 'git-call-process-display-error "update-index" "--" files) - (git-update-status-files files) - (git-success-message "Resolved" files))))) - (defun git-remove-handled () "Remove handled files from the status list." (interactive) @@ -1556,7 +1547,6 @@ amended version of it." (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) - (define-key map "R" 'git-resolve-file) (define-key map "t" toggle-map) (define-key map "T" 'git-toggle-all-marks) (define-key map "u" 'git-unmark-file) @@ -1598,7 +1588,6 @@ amended version of it." ("Merge" ["Next Unmerged File" git-next-unmerged-file t] ["Prev Unmerged File" git-prev-unmerged-file t] - ["Mark as Resolved" git-resolve-file t] ["Interactive Merge File" git-find-file-imerge t] ["Diff Against Common Base File" git-diff-file-base t] ["Diff Combined" git-diff-file-combined t] diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 38438f3..e710219 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -8,12 +8,10 @@ # License: MIT <http://www.opensource.org/licenses/mit-license.php> # -import optparse, sys, os, marshal, popen2, subprocess, shelve -import tempfile, getopt, sha, os.path, time, platform +import optparse, sys, os, marshal, subprocess, shelve +import tempfile, getopt, os.path, time, platform import re -from sets import Set; - verbose = False @@ -864,8 +862,8 @@ class P4Sync(Command): self.usage += " //depot/path[@revRange]" self.silent = False - self.createdBranches = Set() - self.committedChanges = Set() + self.createdBranches = set() + self.committedChanges = set() self.branch = "" self.detectBranches = False self.detectLabels = False @@ -1662,7 +1660,7 @@ class P4Sync(Command): if len(self.changesFile) > 0: output = open(self.changesFile).readlines() - changeSet = Set() + changeSet = set() for line in output: changeSet.add(int(line)) diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl new file mode 100755 index 0000000..5782d80 --- /dev/null +++ b/contrib/fast-import/import-directories.perl @@ -0,0 +1,416 @@ +#!/usr/bin/perl -w +# +# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se> +# +# ------------------------------------------------------------------------ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# ------------------------------------------------------------------------ + +=pod + +=head1 NAME + +import-directories - Import bits and pieces to Git. + +=head1 SYNOPSIS + +B<import-directories.perl> F<configfile> F<outputfile> + +=head1 DESCRIPTION + +Script to import arbitrary projects version controlled by the "copy the +source directory to a new location and edit it there"-version controlled +projects into version control. Handles projects with arbitrary branching +and version trees, taking a file describing the inputs and generating a +file compatible with the L<git-fast-import(1)> format. + +=head1 CONFIGURATION FILE + +=head2 Format + +The configuration file is based on the standard I<.ini> format. + + ; Comments start with semi-colons + [section] + key=value + +Please see below for information on how to escape special characters. + +=head2 Global configuration + +Global configuration is done in the B<[config]> section, which should be +the first section in the file. Configuration can be changed by +repeating configuration sections later on. + + [config] + ; configure conversion of CRLFs. "convert" means that all CRLFs + ; should be converted into LFs (suitable for the core.autocrlf + ; setting set to true in Git). "none" means that all data is + ; treated as binary. + crlf=convert + +=head2 Revision configuration + +Each revision that is to be imported is described in three +sections. Revisions should be defined in topological order, so +that a revision's parent has always been defined when a new revision +is introduced. All the sections for one revision must be defined +before defining the next revision. + +Each revision is assigned a unique numerical identifier. The +numbers do not need to be consecutive, nor monotonically +increasing. + +For instance, if your configuration file contains only the two +revisions 4711 and 42, where 4711 is the initial commit, the +only requirement is that 4711 is completely defined before 42. + +=pod + +=head3 Revision description section + +A section whose section name is just an integer gives meta-data +about the revision. + + [3] + ; author sets the author of the revisions + author=Peter Krefting <peter@softwolves.pp.se> + ; branch sets the branch that the revision should be committed to + branch=master + ; parent describes the revision that is the parent of this commit + ; (optional) + parent=1 + ; merges describes a revision that is merged into this commit + ; (optional; can be repeated) + merges=2 + ; selects one file to take the timestamp from + ; (optional; if unspecified, the most recent file from the .files + ; section is used) + timestamp=3/source.c + +=head3 Revision contents section + +A section whose section name is an integer followed by B<.files> +describe all the files included in this revision. If a file that +was available previously is not included in this revision, it will +be removed. + +If an on-disk revision is incomplete, you can point to files from +a previous revision. There are no restriction as to where the source +files are located, nor to the names of them. + + [3.files] + ; the key is the path inside the repository, the value is the path + ; as seen from the importer script. + source.c=ver-3.00/source.c + source.h=ver-2.99/source.h + readme.txt=ver-3.00/introduction to the project.txt + +File names are treated as byte strings (but please see below on +quoting rules), and should be stored in the configuration file in +the encoding that should be used in the generated repository. + +=head3 Revision commit message section + +A section whose section name is an integer followed by B<.message> +gives the commit message. This section is read verbatim, up until +the beginning of the next section. As such, a commit message may not +contain a line that begins with an opening square bracket ("[") and +ends with a closing square bracket ("]"), unless they are surrounded +by whitespace or other characters. + + [3.message] + Implement foobar. + ; trailing blank lines are ignored. + +=cut + +# Globals +use strict; +use integer; +my $crlfmode = 0; +my @revs; +my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource); +my $sectiontype = 0; +my $rev = 0; +my $mark = 1; + +# Check command line +if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/) +{ + exec('perldoc', $0); + exit 1; +} + +# Open configuration +my $config = $ARGV[0]; +open CFG, '<', $config or die "Cannot open configuration file \"$config\": "; + +# Open output +my $output = $ARGV[1]; +open OUT, '>', $output or die "Cannot create output file \"$output\": "; +binmode OUT; + +LINE: while (my $line = <CFG>) +{ + $line =~ s/\r?\n$//; + next LINE if $sectiontype != 4 && $line eq ''; + next LINE if $line =~ /^;/; + my $oldsectiontype = $sectiontype; + my $oldrev = $rev; + + # Sections + if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$") + { + if ($1 eq 'config') + { + $sectiontype = 1; + } + elsif ($3 eq '') + { + $sectiontype = 2; + $rev = $2; + # Create a new revision + die "Duplicate rev: $line\n " if defined $revmap{$rev}; + print "Reading revision $rev\n"; + push @revs, $rev; + $revmap{$rev} = $mark ++; + $time{$revmap{$rev}} = 0; + } + elsif ($3 eq '.files') + { + $sectiontype = 3; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + elsif ($3 eq '.message') + { + $sectiontype = 4; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + else + { + die "Internal parse error: $line\n "; + } + next LINE; + } + + # Parse data + if ($sectiontype != 4) + { + # Key and value + if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$") + { + my ($key, $value) = &parsekeyvaluepair($1); + # Global configuration + if (1 == $sectiontype) + { + if ($key eq 'crlf') + { + $crlfmode = 1, next LINE if $value eq 'convert'; + $crlfmode = 0, next LINE if $value eq 'none'; + } + die "Unknown configuration option: $line\n "; + } + # Revision specification + if (2 == $sectiontype) + { + my $current = $revmap{$rev}; + $author{$current} = $value, next LINE if $key eq 'author'; + $branch{$current} = $value, next LINE if $key eq 'branch'; + $parent{$current} = $value, next LINE if $key eq 'parent'; + $timesource{$current} = $value, next LINE if $key eq 'timestamp'; + push(@{$merges{$current}}, $value), next LINE if $key eq 'merges'; + die "Unknown revision option: $line\n "; + } + # Filespecs + if (3 == $sectiontype) + { + # Add the file and create a marker + die "File not found: $line\n " unless -f $value; + my $current = $revmap{$rev}; + ${$files{$current}}{$key} = $mark; + my $time = &fileblob($value, $crlfmode, $mark ++); + + # Update revision timestamp if more recent than other + # files seen, or if this is the file we have selected + # to take the time stamp from using the "timestamp" + # directive. + if ((defined $timesource{$current} && $timesource{$current} eq $value) + || $time > $time{$current}) + { + $time{$current} = $time; + } + } + } + else + { + die "Parse error: $line\n "; + } + } + else + { + # Commit message + my $current = $revmap{$rev}; + if (defined $message{$current}) + { + $message{$current} .= "\n"; + } + $message{$current} .= $line; + } +} +close CFG; + +# Start spewing out data for git-fast-import +foreach my $commit (@revs) +{ + # Progress + print OUT "progress Creating revision $commit\n"; + + # Create commit header + my $mark = $revmap{$commit}; + + # Branch and commit id + print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n"; + + # Author and timestamp + die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark}; + print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n"; + + # Commit message + die "No message defined for $commit\n" unless defined $message{$mark}; + my $message = $message{$mark}; + $message =~ s/\n$//; # Kill trailing empty line + print OUT "data ", length($message), "\n", $message, "\n"; + + # Parent and any merges + print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark}; + if (defined $merges{$mark}) + { + foreach my $merge (@{$merges{$mark}}) + { + print OUT "merge :", $revmap{$merge}, "\n"; + } + } + + # Output file marks + print OUT "deleteall\n"; # start from scratch + foreach my $file (sort keys %{$files{$mark}}) + { + print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n"; + } + print OUT "\n"; +} + +# Create one file blob +sub fileblob +{ + my ($filename, $crlfmode, $mark) = @_; + + # Import the file + print OUT "progress Importing $filename\nblob\nmark :$mark\n"; + open FILE, '<', $filename or die "Cannot read $filename\n "; + binmode FILE; + my ($size, $mtime) = (stat(FILE))[7,9]; + my $file; + read FILE, $file, $size; + close FILE; + $file =~ s/\r\n/\n/g if $crlfmode; + print OUT "data ", length($file), "\n", $file, "\n"; + + return $mtime; +} + +# Parse a key=value pair +sub parsekeyvaluepair +{ +=pod + +=head2 Escaping special characters + +Key and value strings may be enclosed in quotes, in which case +whitespace inside the quotes is preserved. Additionally, an equal +sign may be included in the key by preceeding it with a backslash. +For example: + + "key1 "=value1 + key2=" value2" + key\=3=value3 + key4=value=4 + "key5""=value5 + +Here the first key is "key1 " (note the trailing white-space) and the +second value is " value2" (note the leading white-space). The third +key contains an equal sign "key=3" and so does the fourth value, which +does not need to be escaped. The fifth key contains a trailing quote, +which does not need to be escaped since it is inside a surrounding +quote. + +=cut + my $pair = shift; + + # Separate key and value by the first non-quoted equal sign + my ($key, $value); + if ($pair =~ /^(.*[^\\])=(.*)$/) + { + ($key, $value) = ($1, $2) + } + else + { + die "Parse error: $pair\n "; + } + + # Unquote and unescape the key and value separately + return (&unescape($key), &unescape($value)); +} + +# Unquote and unescape +sub unescape +{ + my $string = shift; + + # First remove enclosing quotes. Backslash before the trailing + # quote leaves both. + if ($string =~ /^"(.*[^\\])"$/) + { + $string = $1; + } + + # Second remove any backslashes inside the unquoted string. + # For later: Handle special sequences like \t ? + $string =~ s/\\(.)/$1/g; + + return $string; +} + +__END__ + +=pod + +=head1 EXAMPLES + +B<import-directories.perl> F<project.import> + +=head1 AUTHOR + +Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation. + +=cut @@ -35,6 +35,19 @@ int copy_fd(int ifd, int ofd) return 0; } +static int copy_times(const char *dst, const char *src) +{ + struct stat st; + struct utimbuf times; + if (stat(src, &st) < 0) + return -1; + times.actime = st.st_atime; + times.modtime = st.st_mtime; + if (utime(dst, ×) < 0) + return -1; + return 0; +} + int copy_file(const char *dst, const char *src, int mode) { int fdi, fdo, status; @@ -55,3 +68,11 @@ int copy_file(const char *dst, const char *src, int mode) return status; } + +int copy_file_with_time(const char *dst, const char *src, int mode) +{ + int status = copy_file(dst, src, mode); + if (!status) + return copy_times(dst, src); + return status; +} diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 06f7060..392efb9 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -72,6 +72,79 @@ sub colored { # command line options my $patch_mode; +my $patch_mode_revision; + +sub apply_patch; +sub apply_patch_for_checkout_commit; +sub apply_patch_for_stash; + +my %patch_modes = ( + 'stage' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stage', + TARGET => '', + PARTICIPLE => 'staging', + FILTER => 'file-only', + }, + 'stash' => { + DIFF => 'diff-index -p HEAD', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stash', + TARGET => '', + PARTICIPLE => 'stashing', + FILTER => undef, + }, + 'reset_head' => { + DIFF => 'diff-index -p --cached', + APPLY => sub { apply_patch 'apply -R --cached', @_; }, + APPLY_CHECK => 'apply -R --cached', + VERB => 'Unstage', + TARGET => '', + PARTICIPLE => 'unstaging', + FILTER => 'index-only', + }, + 'reset_nothead' => { + DIFF => 'diff-index -R -p --cached', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Apply', + TARGET => ' to index', + PARTICIPLE => 'applying', + FILTER => 'index-only', + }, + 'checkout_index' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply -R', @_; }, + APPLY_CHECK => 'apply -R', + VERB => 'Discard', + TARGET => ' from worktree', + PARTICIPLE => 'discarding', + FILTER => 'file-only', + }, + 'checkout_head' => { + DIFF => 'diff-index -p', + APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, + APPLY_CHECK => 'apply -R', + VERB => 'Discard', + TARGET => ' from index and worktree', + PARTICIPLE => 'discarding', + FILTER => undef, + }, + 'checkout_nothead' => { + DIFF => 'diff-index -R -p', + APPLY => sub { apply_patch_for_checkout_commit '', @_ }, + APPLY_CHECK => 'apply', + VERB => 'Apply', + TARGET => ' to index and worktree', + PARTICIPLE => 'applying', + FILTER => undef, + }, +); + +my %patch_mode_flavour = %{$patch_modes{stage}}; sub run_cmd_pipe { if ($^O eq 'MSWin32' || $^O eq 'msys') { @@ -190,7 +263,14 @@ sub list_modified { return if (!@tracked); } - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference; + if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') { + $reference = $patch_mode_revision; + } elsif (is_initial_commit()) { + $reference = get_empty_tree(); + } else { + $reference = 'HEAD'; + } for (run_cmd_pipe(qw(git diff-index --cached --numstat --summary), $reference, '--', @tracked)) { @@ -613,12 +693,24 @@ sub add_untracked_cmd { print "\n"; } +sub run_git_apply { + my $cmd = shift; + my $fh; + open $fh, '| git ' . $cmd; + print $fh @_; + return close $fh; +} + sub parse_diff { my ($path) = @_; - my @diff = run_cmd_pipe(qw(git diff-files -p --), $path); + my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + if (defined $patch_mode_revision) { + push @diff_cmd, $patch_mode_revision; + } + my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path); + @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -881,6 +973,7 @@ sub edit_hunk_manually { or die "failed to open hunk edit file for writing: " . $!; print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; + my $participle = $patch_mode_flavour{PARTICIPLE}; print $fh <<EOF; # --- # To remove '-' lines, make them ' ' lines (context). @@ -888,7 +981,7 @@ sub edit_hunk_manually { # Lines starting with # will be removed. # # If the patch applies cleanly, the edited hunk will immediately be -# marked for staging. If it does not apply cleanly, you will be given +# marked for $participle. If it does not apply cleanly, you will be given # an opportunity to edit again. If all lines of the hunk are removed, # then the edit is aborted and the hunk is left unchanged. EOF @@ -922,11 +1015,8 @@ EOF sub diff_applies { my $fh; - open $fh, '| git apply --recount --cached --check'; - for my $h (@_) { - print $fh @{$h->{TEXT}}; - } - return close $fh; + return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check', + map { @{$_->{TEXT}} } @_); } sub _restore_terminal_and_die { @@ -992,12 +1082,14 @@ sub edit_hunk_loop { } sub help_patch_cmd { - print colored $help_color, <<\EOF ; -y - stage this hunk -n - do not stage this hunk -q - quit, do not stage this hunk nor any of the remaining ones -a - stage this and all the remaining hunks in the file -d - do not stage this hunk nor any of the remaining hunks in the file + my $verb = lc $patch_mode_flavour{VERB}; + my $target = $patch_mode_flavour{TARGET}; + print colored $help_color, <<EOF ; +y - $verb this hunk$target +n - do not $verb this hunk$target +q - quit, do not $verb this hunk nor any of the remaining ones +a - $verb this and all the remaining hunks in the file +d - do not $verb this hunk nor any of the remaining hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -1010,8 +1102,40 @@ e - manually edit the current hunk EOF } +sub apply_patch { + my $cmd = shift; + my $ret = run_git_apply $cmd . ' --recount', @_; + if (!$ret) { + print STDERR @_; + } + return $ret; +} + +sub apply_patch_for_checkout_commit { + my $reverse = shift; + my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_; + my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_; + + if ($applies_worktree && $applies_index) { + run_git_apply 'apply '.$reverse.' --cached --recount', @_; + run_git_apply 'apply '.$reverse.' --recount', @_; + return 1; + } elsif (!$applies_index) { + print colored $error_color, "The selected hunks do not apply to the index!\n"; + if (prompt_yesno "Apply them to the worktree anyway? ") { + return run_git_apply 'apply '.$reverse.' --recount', @_; + } else { + print colored $error_color, "Nothing was applied.\n"; + return 0; + } + } else { + print STDERR @_; + return 0; + } +} + sub patch_update_cmd { - my @all_mods = list_modified('file-only'); + my @all_mods = list_modified($patch_mode_flavour{FILTER}); my @mods = grep { !($_->{BINARY}) } @all_mods; my @them; @@ -1142,8 +1266,9 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, 'Stage ', - ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'), + print colored $prompt_color, $patch_mode_flavour{VERB}, + ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'), + $patch_mode_flavour{TARGET}, " [y,n,q,a,d,/$other,?]? "; my $line = prompt_single_character; if ($line) { @@ -1317,16 +1442,9 @@ sub patch_update_file { if (@result) { my $fh; - - open $fh, '| git apply --cached --recount'; - for (@{$head->{TEXT}}, @result) { - print $fh $_; - } - if (!close $fh) { - for (@{$head->{TEXT}}, @result) { - print STDERR $_; - } - } + my @patch = (@{$head->{TEXT}}, @result); + my $apply_routine = $patch_mode_flavour{APPLY}; + &$apply_routine(@patch); refresh(); } @@ -1367,11 +1485,41 @@ EOF sub process_args { return unless @ARGV; my $arg = shift @ARGV; - if ($arg eq "--patch") { - $patch_mode = 1; - $arg = shift @ARGV or die "missing --"; + if ($arg =~ /--patch(?:=(.*))?/) { + if (defined $1) { + if ($1 eq 'reset') { + $patch_mode = 'reset_head'; + $patch_mode_revision = 'HEAD'; + $arg = shift @ARGV or die "missing --"; + if ($arg ne '--') { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'reset_head' : 'reset_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'checkout') { + $arg = shift @ARGV or die "missing --"; + if ($arg eq '--') { + $patch_mode = 'checkout_index'; + } else { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'checkout_head' : 'checkout_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'stage' or $1 eq 'stash') { + $patch_mode = $1; + $arg = shift @ARGV or die "missing --"; + } else { + die "unknown --patch mode: $1"; + } + } else { + $patch_mode = 'stage'; + $arg = shift @ARGV or die "missing --"; + } die "invalid argument $arg, expecting --" unless $arg eq "--"; + %patch_mode_flavour = %{$patch_modes{$patch_mode}}; } elsif ($arg ne "--") { die "invalid argument $arg, expecting --"; @@ -15,6 +15,7 @@ q,quiet be quiet s,signoff add a Signed-off-by line to the commit message u,utf8 recode into utf8 (default) k,keep pass -k flag to git-mailinfo +c,scissors strip everything before a scissors line whitespace= pass it through git-apply ignore-space-change pass it through git-apply ignore-whitespace pass it through git-apply @@ -288,7 +289,7 @@ split_patches () { prec=4 dotest="$GIT_DIR/rebase-apply" sign= utf8=t keep= skip= interactive= resolved= rebasing= abort= -resolvemsg= resume= +resolvemsg= resume= scissors= git_apply_opt= committer_date_is_author_date= ignore_date= @@ -310,6 +311,10 @@ do utf8= ;; -k|--keep) keep=t ;; + -c|--scissors) + scissors=t ;; + --no-scissors) + scissors=f ;; -r|--resolved) resolved=t ;; --skip) @@ -317,7 +322,7 @@ do --abort) abort=t ;; --rebasing) - rebasing=t threeway=t keep=t ;; + rebasing=t threeway=t keep=t scissors=f ;; -d|--dotest) die "-d option is no longer supported. Do not use." ;; @@ -435,14 +440,14 @@ else split_patches "$@" - # -s, -u, -k, --whitespace, -3, -C, -q and -p flags are kept - # for the resuming session after a patch failure. - # -i can and must be given when resuming. + # -i can and must be given when resuming; everything + # else is kept echo " $git_apply_opt" >"$dotest/apply-opt" echo "$threeway" >"$dotest/threeway" echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" + echo "$scissors" >"$dotest/scissors" echo "$GIT_QUIET" >"$dotest/quiet" echo 1 >"$dotest/next" if test -n "$rebasing" @@ -484,6 +489,12 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi +case "$(cat "$dotest/scissors")" in +t) + scissors=--scissors ;; +f) + scissors=--no-scissors ;; +esac if test "$(cat "$dotest/quiet")" = t then GIT_QUIET=t @@ -538,7 +549,7 @@ do # by the user, or the user can tell us to do so by --resolved flag. case "$resume" in '') - git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ + git mailinfo $keep $scissors $utf8 "$dotest/msg" "$dotest/patch" \ <"$dotest/$msgnum" >"$dotest/info" || stop_here $this diff --git a/git-cvsimport.perl b/git-cvsimport.perl index 593832d..d741115 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -238,7 +238,9 @@ sub conn { } my $rr = ":pserver:$user\@$serv:$port$repo"; - unless ($pass) { + if ($pass) { + $pass = $self->_scramble($pass); + } else { open(H,$ENV{'HOME'}."/.cvspass") and do { # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z while (<H>) { @@ -253,8 +255,6 @@ sub conn { }; } - $pass = $self->_scramble($pass); - my ($s, $rep); if ($proxyhost) { diff --git a/git-cvsserver.perl b/git-cvsserver.perl index ab6cea3..6dc45f5 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -285,7 +285,7 @@ sub req_Root return 0; } - my @gitvars = `git-config -l`; + my @gitvars = `git config -l`; if ($?) { print "E problems executing git-config on the server -- this is not a git repository or the PATH is not set correctly.\n"; print "E \n"; @@ -702,7 +702,7 @@ sub req_Modified # Save the file data in $state $state->{entries}{$state->{directory}.$data}{modified_filename} = $filename; $state->{entries}{$state->{directory}.$data}{modified_mode} = $mode; - $state->{entries}{$state->{directory}.$data}{modified_hash} = `git-hash-object $filename`; + $state->{entries}{$state->{directory}.$data}{modified_hash} = `git hash-object $filename`; $state->{entries}{$state->{directory}.$data}{modified_hash} =~ s/\s.*$//s; #$log->debug("req_Modified : file=$data mode=$mode size=$size"); @@ -1289,7 +1289,7 @@ sub req_ci # do a checkout of the file if it is part of this tree if ($wrev) { - system('git-checkout-index', '-f', '-u', $filename); + system('git', 'checkout-index', '-f', '-u', $filename); unless ($? == 0) { die "Error running git-checkout-index -f -u $filename : $!"; } @@ -1331,15 +1331,15 @@ sub req_ci { $log->info("Removing file '$filename'"); unlink($filename); - system("git-update-index", "--remove", $filename); + system("git", "update-index", "--remove", $filename); } elsif ( $addflag ) { $log->info("Adding file '$filename'"); - system("git-update-index", "--add", $filename); + system("git", "update-index", "--add", $filename); } else { $log->info("Updating file '$filename'"); - system("git-update-index", $filename); + system("git", "update-index", $filename); } } @@ -1351,7 +1351,7 @@ sub req_ci return; } - my $treehash = `git-write-tree`; + my $treehash = `git write-tree`; chomp $treehash; $log->debug("Treehash : $treehash, Parenthash : $parenthash"); @@ -1368,7 +1368,7 @@ sub req_ci } close $msg_fh; - my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`; + my $commithash = `git commit-tree $treehash -p $parenthash < $msg_filename`; chomp($commithash); $log->info("Commit hash : $commithash"); @@ -1821,7 +1821,7 @@ sub req_annotate # TODO: if we got a revision from the client, use that instead # to look up the commithash in sqlite (still good to default to # the current head as we do now) - system("git-read-tree", $lastseenin); + system("git", "read-tree", $lastseenin); unless ($? == 0) { print "E error running git-read-tree $lastseenin $ENV{GIT_INDEX_FILE} $!\n"; @@ -1830,7 +1830,7 @@ sub req_annotate $log->info("Created index '$ENV{GIT_INDEX_FILE}' with commit $lastseenin - exit status $?"); # do a checkout of the file - system('git-checkout-index', '-f', '-u', $filename); + system('git', 'checkout-index', '-f', '-u', $filename); unless ($? == 0) { print "E error running git-checkout-index -f -u $filename : $!\n"; return; @@ -1861,7 +1861,7 @@ sub req_annotate close ANNOTATEHINTS or (print "E failed to write $a_hints: $!\n"), return; - my @cmd = (qw(git-annotate -l -S), $a_hints, $filename); + my @cmd = (qw(git annotate -l -S), $a_hints, $filename); if (!open(ANNOTATE, "-|", @cmd)) { print "E error invoking ". join(' ',@cmd) .": $!\n"; return; @@ -2078,17 +2078,17 @@ sub transmitfile die "Need filehash" unless ( defined ( $filehash ) and $filehash =~ /^[a-zA-Z0-9]{40}$/ ); - my $type = `git-cat-file -t $filehash`; + my $type = `git cat-file -t $filehash`; chomp $type; die ( "Invalid type '$type' (expected 'blob')" ) unless ( defined ( $type ) and $type eq "blob" ); - my $size = `git-cat-file -s $filehash`; + my $size = `git cat-file -s $filehash`; chomp $size; $log->debug("transmitfile($filehash) size=$size, type=$type"); - if ( open my $fh, '-|', "git-cat-file", "blob", $filehash ) + if ( open my $fh, '-|', "git", "cat-file", "blob", $filehash ) { if ( defined ( $options->{targetfile} ) ) { @@ -2935,7 +2935,7 @@ sub update push @git_log_params, $self->{module}; } # git-rev-list is the backend / plumbing version of git-log - open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!"; + open(GITLOG, '-|', 'git', 'rev-list', @git_log_params) or die "Cannot call git-rev-list: $!"; my @commits; @@ -3021,7 +3021,7 @@ sub update next; } my $base = eval { - safe_pipe_capture('git-merge-base', + safe_pipe_capture('git', 'merge-base', $lastpicked, $parent); }; # The two branches may not be related at all, @@ -3033,7 +3033,7 @@ sub update if ($base) { my @merged; # print "want to log between $base $parent \n"; - open(GITLOG, '-|', 'git-log', '--pretty=medium', "$base..$parent") + open(GITLOG, '-|', 'git', 'log', '--pretty=medium', "$base..$parent") or die "Cannot call git-log: $!"; my $mergedhash; while (<GITLOG>) { @@ -3075,7 +3075,7 @@ sub update if ( defined ( $lastpicked ) ) { - my $filepipe = open(FILELIST, '-|', 'git-diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!"); + my $filepipe = open(FILELIST, '-|', 'git', 'diff-tree', '-z', '-r', $lastpicked, $commit->{hash}) or die("Cannot call git-diff-tree : $!"); local ($/) = "\0"; while ( <FILELIST> ) { @@ -3149,7 +3149,7 @@ sub update # this is used to detect files removed from the repo my $seen_files = {}; - my $filepipe = open(FILELIST, '-|', 'git-ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!"); + my $filepipe = open(FILELIST, '-|', 'git', 'ls-tree', '-z', '-r', $commit->{hash}) or die("Cannot call git-ls-tree : $!"); local $/ = "\0"; while ( <FILELIST> ) { @@ -3451,7 +3451,7 @@ sub commitmessage return $message; } - my @lines = safe_pipe_capture("git-cat-file", "commit", $commithash); + my @lines = safe_pipe_capture("git", "cat-file", "commit", $commithash); shift @lines while ( $lines[0] =~ /\S/ ); $message = join("",@lines); $message .= " " if ( $message =~ /\n$/ ); diff --git a/git-rebase.sh b/git-rebase.sh index 2315d95..6ec155c 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -387,7 +387,7 @@ fi # The tree must be really really clean. if ! git update-index --ignore-submodules --refresh > /dev/null; then echo >&2 "cannot rebase: you have unstaged changes" - git diff --name-status -r --ignore-submodules -- >&2 + git diff-files --name-status -r --ignore-submodules -- >&2 exit 1 fi diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) diff --git a/git-stash.sh b/git-stash.sh index d61c9d0..4febbbf 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -7,7 +7,7 @@ USAGE="list [<options>] or: $dashless drop [-q|--quiet] [<stash>] or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>] or: $dashless branch <branchname> [<stash>] - or: $dashless [save [--keep-index] [-q|--quiet] [<message>]] + or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]] or: $dashless clear" SUBDIRECTORY_OK=Yes @@ -21,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0 ref_stash=refs/stash +if git config --get-colorbool color.interactive; then + help_color="$(git config --get-color color.interactive.help 'red bold')" + reset_color="$(git config --get-color '' reset)" +else + help_color= + reset_color= +fi + no_changes () { git diff-index --quiet --cached HEAD --ignore-submodules -- && git diff-files --quiet --ignore-submodules @@ -68,19 +76,44 @@ create_stash () { git commit-tree $i_tree -p $b_commit) || die "Cannot save the current index state" - # state of the working tree - w_tree=$( ( + if test -z "$patch_mode" + then + + # state of the working tree + w_tree=$( ( + rm -f "$TMP-index" && + cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && + GIT_INDEX_FILE="$TMP-index" && + export GIT_INDEX_FILE && + git read-tree -m $i_tree && + git add -u && + git write-tree && + rm -f "$TMP-index" + ) ) || + die "Cannot save the current worktree state" + + else + rm -f "$TMP-index" && - cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && - GIT_INDEX_FILE="$TMP-index" && - export GIT_INDEX_FILE && - git read-tree -m $i_tree && - git add -u && - git write-tree && - rm -f "$TMP-index" - ) ) || + GIT_INDEX_FILE="$TMP-index" git read-tree HEAD && + + # find out what the user wants + GIT_INDEX_FILE="$TMP-index" \ + git add--interactive --patch=stash -- && + + # state of the working tree + w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) || die "Cannot save the current worktree state" + git diff-tree -p HEAD $w_tree > "$TMP-patch" && + test -s "$TMP-patch" || + die "No changes selected" + + rm -f "$TMP-index" || + die "Cannot remove temporary index (can't happen)" + + fi + # create the stash if test -z "$stash_msg" then @@ -95,15 +128,31 @@ create_stash () { save_stash () { keep_index= + patch_mode= while test $# != 0 do case "$1" in - --keep-index) + -k|--keep-index) + keep_index=t + ;; + --no-keep-index) + keep_index= + ;; + -p|--patch) + patch_mode=t keep_index=t ;; -q|--quiet) GIT_QUIET=t ;; + --) + shift + break + ;; + -*) + echo "error: unknown option for 'stash save': $1" + usage + ;; *) break ;; @@ -131,11 +180,22 @@ save_stash () { die "Cannot save the current status" say Saved working directory and index state "$stash_msg" - git reset --hard ${GIT_QUIET:+-q} - - if test -n "$keep_index" && test -n $i_tree + if test -z "$patch_mode" then - git read-tree --reset -u $i_tree + git reset --hard ${GIT_QUIET:+-q} + + if test -n "$keep_index" && test -n $i_tree + then + git read-tree --reset -u $i_tree + fi + else + git apply -R < "$TMP-patch" || + die "Cannot remove worktree changes" + + if test -z "$keep_index" + then + git reset + fi fi } @@ -307,6 +367,18 @@ apply_to_branch () { drop_stash $stash } +# The default command is "save" if nothing but options are given +seen_non_option= +for opt +do + case "$opt" in + -*) ;; + *) seen_non_option=t; break ;; + esac +done + +test -n "$seen_non_option" || set "save" "$@" + # Main command set case "$1" in list) @@ -358,12 +430,13 @@ branch) apply_to_branch "$@" ;; *) - if test $# -eq 0 - then + case $# in + 0) save_stash && say '(To restore them type "git stash apply")' - else + ;; + *) usage - fi + esac ;; esac diff --git a/git-svn.perl b/git-svn.perl index ce4fef9..e0ec258 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -2836,6 +2836,7 @@ sub other_gs { sub call_authors_prog { my ($orig_author) = @_; + $orig_author = command_oneline('rev-parse', '--sq-quote', $orig_author); my $author = `$::_authors_prog $orig_author`; if ($? != 0) { die "$::_authors_prog failed with exit code $?\n" @@ -5,7 +5,10 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" + " [-p|--paginate|--no-pager]\n" + " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" + " [--help] COMMAND [ARGS]"; const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; @@ -309,9 +312,6 @@ static void handle_internal_command(int argc, const char **argv) { "get-tar-commit-id", cmd_get_tar_commit_id }, { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, { "help", cmd_help }, -#ifndef NO_CURL - { "http-fetch", cmd_http_fetch, RUN_SETUP }, -#endif { "init", cmd_init_db }, { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP | USE_PAGER }, @@ -59,6 +59,7 @@ struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; struct grep_expr *pattern_expression; + const char *prefix; int prefix_length; regex_t regexp; int linenum; diff --git a/builtin-http-fetch.c b/http-fetch.c index f3e63d7..e8f44ba 100644 --- a/builtin-http-fetch.c +++ b/http-fetch.c @@ -1,8 +1,9 @@ #include "cache.h" #include "walker.h" -int cmd_http_fetch(int argc, const char **argv, const char *prefix) +int main(int argc, const char **argv) { + const char *prefix; struct walker *walker; int commits_on_stdin = 0; int commits; @@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix) int get_verbosely = 0; int get_recover = 0; + prefix = setup_git_directory(); + git_config(git_default_config, NULL); while (arg < argc && argv[arg][0] == '-') { @@ -869,17 +869,6 @@ static int fetch_pack_index(unsigned char *sha1, const char *base_url) char *url; struct strbuf buf = STRBUF_INIT; - /* Don't use the index if the pack isn't there */ - end_url_with_slash(&buf, base_url); - strbuf_addf(&buf, "objects/pack/pack-%s.pack", hex); - url = strbuf_detach(&buf, 0); - - if (http_get_strbuf(url, NULL, 0)) { - ret = error("Unable to verify pack %s is available", - hex); - goto cleanup; - } - if (has_pack_index(sha1)) { ret = 0; goto cleanup; diff --git a/merge-recursive.c b/merge-recursive.c index 10d7913..f55b7eb 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -170,6 +170,18 @@ static int git_merge_trees(int index_only, int rc; struct tree_desc t[3]; struct unpack_trees_options opts; + static const struct unpack_trees_error_msgs msgs = { + /* would_overwrite */ + "Your local changes to '%s' would be overwritten by merge. Aborting.", + /* not_uptodate_file */ + "Your local changes to '%s' would be overwritten by merge. Aborting.", + /* not_uptodate_dir */ + "Updating '%s' would lose untracked files in it. Aborting.", + /* would_lose_untracked */ + "Untracked working tree file '%s' would be %s by merge. Aborting", + /* bind_overlap -- will not happen here */ + NULL, + }; memset(&opts, 0, sizeof(opts)); if (index_only) @@ -181,6 +193,7 @@ static int git_merge_trees(int index_only, opts.fn = threeway_merge; opts.src_index = &the_index; opts.dst_index = &the_index; + opts.msgs = msgs; init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); @@ -1188,10 +1201,14 @@ int merge_trees(struct merge_options *o, code = git_merge_trees(o->call_depth, common, head, merge); - if (code != 0) - die("merging of trees %s and %s failed", - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1)); + if (code != 0) { + if (show(o, 4) || o->call_depth) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + else + exit(128); + } if (unmerged_cache()) { struct string_list *entries, *re_head, *re_merge; @@ -21,8 +21,6 @@ static void pager_preexec(void) FD_ZERO(&in); FD_SET(0, &in); select(1, &in, NULL, &in, NULL); - - setenv("LESS", "FRSX", 0); } #endif @@ -70,6 +68,10 @@ void setup_pager(void) pager_argv[2] = pager; pager_process.argv = pager_argv; pager_process.in = -1; + if (!getenv("LESS")) { + static const char *env[] = { "LESS=FRSX", NULL }; + pager_process.env = env; + } #ifndef __MINGW32__ pager_process.preexec_cb = pager_preexec; #endif diff --git a/remote-curl.c b/remote-curl.c new file mode 100644 index 0000000..ad6a163 --- /dev/null +++ b/remote-curl.c @@ -0,0 +1,139 @@ +#include "cache.h" +#include "remote.h" +#include "strbuf.h" +#include "walker.h" +#include "http.h" + +static struct ref *get_refs(struct walker *walker, const char *url) +{ + struct strbuf buffer = STRBUF_INIT; + char *data, *start, *mid; + char *ref_name; + char *refs_url; + int i = 0; + int http_ret; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + refs_url = xmalloc(strlen(url) + 11); + sprintf(refs_url, "%s/info/refs", url); + + http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); + switch (http_ret) { + case HTTP_OK: + break; + case HTTP_MISSING_TARGET: + die("%s not found: did you run git update-server-info on the" + " server?", refs_url); + default: + http_error(refs_url, http_ret); + die("HTTP request failed"); + } + + data = buffer.buf; + start = NULL; + mid = data; + while (i < buffer.len) { + if (!start) { + start = &data[i]; + } + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + strbuf_release(&buffer); + + ref = alloc_ref("HEAD"); + if (!walker->fetch_ref(walker, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + + strbuf_release(&buffer); + free(refs_url); + return refs; +} + +int main(int argc, const char **argv) +{ + struct remote *remote; + struct strbuf buf = STRBUF_INIT; + const char *url; + struct walker *walker = NULL; + + setup_git_directory(); + if (argc < 2) { + fprintf(stderr, "Remote needed\n"); + return 1; + } + + remote = remote_get(argv[1]); + + if (argc > 2) { + url = argv[2]; + } else { + url = remote->url[0]; + } + + do { + if (strbuf_getline(&buf, stdin, '\n') == EOF) + break; + if (!prefixcmp(buf.buf, "fetch ")) { + char *obj = buf.buf + strlen("fetch "); + if (!walker) + walker = get_http_walker(url, remote); + walker->get_all = 1; + walker->get_tree = 1; + walker->get_history = 1; + walker->get_verbosely = 0; + walker->get_recover = 0; + if (walker_fetch(walker, 1, &obj, NULL, NULL)) + die("Fetch failed."); + printf("\n"); + fflush(stdout); + } else if (!strcmp(buf.buf, "list")) { + struct ref *refs; + struct ref *posn; + if (!walker) + walker = get_http_walker(url, remote); + refs = get_refs(walker, url); + for (posn = refs; posn; posn = posn->next) { + if (posn->symref) + printf("@%s %s\n", posn->symref, posn->name); + else + printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); + } + printf("\n"); + fflush(stdout); + } else if (!strcmp(buf.buf, "capabilities")) { + printf("fetch\n"); + printf("\n"); + fflush(stdout); + } else { + return 1; + } + strbuf_reset(&buf); + } while (1); + return 0; +} @@ -28,6 +28,11 @@ struct rewrite { int instead_of_nr; int instead_of_alloc; }; +struct rewrites { + struct rewrite **rewrite; + int rewrite_alloc; + int rewrite_nr; +}; static struct remote **remotes; static int remotes_alloc; @@ -41,14 +46,13 @@ static struct branch *current_branch; static const char *default_remote_name; static int explicit_default_remote_name; -static struct rewrite **rewrite; -static int rewrite_alloc; -static int rewrite_nr; +static struct rewrites rewrites; +static struct rewrites rewrites_push; #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; -static const char *alias_url(const char *url) +static const char *alias_url(const char *url, struct rewrites *r) { int i, j; char *ret; @@ -57,14 +61,14 @@ static const char *alias_url(const char *url) longest = NULL; longest_i = -1; - for (i = 0; i < rewrite_nr; i++) { - if (!rewrite[i]) + for (i = 0; i < r->rewrite_nr; i++) { + if (!r->rewrite[i]) continue; - for (j = 0; j < rewrite[i]->instead_of_nr; j++) { - if (!prefixcmp(url, rewrite[i]->instead_of[j].s) && + for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) { + if (!prefixcmp(url, r->rewrite[i]->instead_of[j].s) && (!longest || - longest->len < rewrite[i]->instead_of[j].len)) { - longest = &(rewrite[i]->instead_of[j]); + longest->len < r->rewrite[i]->instead_of[j].len)) { + longest = &(r->rewrite[i]->instead_of[j]); longest_i = i; } } @@ -72,10 +76,10 @@ static const char *alias_url(const char *url) if (!longest) return url; - ret = xmalloc(rewrite[longest_i]->baselen + + ret = xmalloc(r->rewrite[longest_i]->baselen + (strlen(url) - longest->len) + 1); - strcpy(ret, rewrite[longest_i]->base); - strcpy(ret + rewrite[longest_i]->baselen, url + longest->len); + strcpy(ret, r->rewrite[longest_i]->base); + strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len); return ret; } @@ -101,17 +105,25 @@ static void add_url(struct remote *remote, const char *url) remote->url[remote->url_nr++] = url; } -static void add_url_alias(struct remote *remote, const char *url) -{ - add_url(remote, alias_url(url)); -} - static void add_pushurl(struct remote *remote, const char *pushurl) { ALLOC_GROW(remote->pushurl, remote->pushurl_nr + 1, remote->pushurl_alloc); remote->pushurl[remote->pushurl_nr++] = pushurl; } +static void add_pushurl_alias(struct remote *remote, const char *url) +{ + const char *pushurl = alias_url(url, &rewrites_push); + if (pushurl != url) + add_pushurl(remote, pushurl); +} + +static void add_url_alias(struct remote *remote, const char *url) +{ + add_url(remote, alias_url(url, &rewrites)); + add_pushurl_alias(remote, url); +} + static struct remote *make_remote(const char *name, int len) { struct remote *ret; @@ -169,22 +181,22 @@ static struct branch *make_branch(const char *name, int len) return ret; } -static struct rewrite *make_rewrite(const char *base, int len) +static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len) { struct rewrite *ret; int i; - for (i = 0; i < rewrite_nr; i++) { + for (i = 0; i < r->rewrite_nr; i++) { if (len - ? (len == rewrite[i]->baselen && - !strncmp(base, rewrite[i]->base, len)) - : !strcmp(base, rewrite[i]->base)) - return rewrite[i]; + ? (len == r->rewrite[i]->baselen && + !strncmp(base, r->rewrite[i]->base, len)) + : !strcmp(base, r->rewrite[i]->base)) + return r->rewrite[i]; } - ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc); + ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc); ret = xcalloc(1, sizeof(struct rewrite)); - rewrite[rewrite_nr++] = ret; + r->rewrite[r->rewrite_nr++] = ret; if (len) { ret->base = xstrndup(base, len); ret->baselen = len; @@ -355,8 +367,13 @@ static int handle_config(const char *key, const char *value, void *cb) subkey = strrchr(name, '.'); if (!subkey) return 0; - rewrite = make_rewrite(name, subkey - name); if (!strcmp(subkey, ".insteadof")) { + rewrite = make_rewrite(&rewrites, name, subkey - name); + if (!value) + return config_error_nonbool(key); + add_instead_of(rewrite, xstrdup(value)); + } else if (!strcmp(subkey, ".pushinsteadof")) { + rewrite = make_rewrite(&rewrites_push, name, subkey - name); if (!value) return config_error_nonbool(key); add_instead_of(rewrite, xstrdup(value)); @@ -430,13 +447,17 @@ static void alias_all_urls(void) { int i, j; for (i = 0; i < remotes_nr; i++) { + int add_pushurl_aliases; if (!remotes[i]) continue; - for (j = 0; j < remotes[i]->url_nr; j++) { - remotes[i]->url[j] = alias_url(remotes[i]->url[j]); - } for (j = 0; j < remotes[i]->pushurl_nr; j++) { - remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j]); + remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j], &rewrites); + } + add_pushurl_aliases = remotes[i]->pushurl_nr == 0; + for (j = 0; j < remotes[i]->url_nr; j++) { + if (add_pushurl_aliases) + add_pushurl_alias(remotes[i], remotes[i]->url[j]); + remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites); } } } @@ -61,7 +61,7 @@ static int write_rr(struct string_list *rr, int out_fd) path = rr->items[i].string; length = strlen(path) + 1; if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || - write_in_full(out_fd, "\t", 1) != 1 || + write_str_in_full(out_fd, "\t") != 1 || write_in_full(out_fd, path, length) != length) die("unable to write rerere record"); } diff --git a/run-command.c b/run-command.c index f3e7abb..ac314a5 100644 --- a/run-command.c +++ b/run-command.c @@ -173,11 +173,8 @@ fail_pipe: if (cmd->dir) die("chdir in start_command() not implemented"); - if (cmd->env) { - env = copy_environ(); - for (; *cmd->env; cmd->env++) - env = env_setenv(env, *cmd->env); - } + if (cmd->env) + env = make_augmented_environ(cmd->env); if (cmd->git_cmd) { cmd->argv = prepare_git_cmd(cmd->argv); diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh new file mode 100755 index 0000000..75a3ee2 --- /dev/null +++ b/t/lib-patch-mode.sh @@ -0,0 +1,41 @@ +. ./test-lib.sh + +if ! test_have_prereq PERL; then + say 'skipping --patch tests, perl not available' + test_done +fi + +set_state () { + echo "$3" > "$1" && + git add "$1" && + echo "$2" > "$1" +} + +save_state () { + noslash="$(echo "$1" | tr / _)" && + cat "$1" > _worktree_"$noslash" && + git show :"$1" > _index_"$noslash" +} + +set_and_save_state () { + set_state "$@" && + save_state "$1" +} + +verify_state () { + test "$(cat "$1")" = "$2" && + test "$(git show :"$1")" = "$3" +} + +verify_saved_state () { + noslash="$(echo "$1" | tr / _)" && + verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")" +} + +save_head () { + git rev-parse HEAD > _head +} + +verify_saved_head () { + test "$(cat _head)" = "$(git rev-parse HEAD)" +} diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh new file mode 100755 index 0000000..4d1c2e9 --- /dev/null +++ b/t/t2016-checkout-patch.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +test_description='git checkout --patch' + +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add bar dir/foo && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work head && + (echo n; echo n) | git checkout -p && + verify_saved_state bar && + verify_saved_state dir/foo +' + +test_expect_success 'git checkout -p' ' + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p with staged changes' ' + set_state dir/foo work index + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo index index +' + +test_expect_success 'git checkout -p HEAD with NO staged changes: abort' ' + set_and_save_state dir/foo work head && + (echo n; echo y; echo n) | git checkout -p HEAD && + verify_saved_state bar && + verify_saved_state dir/foo +' + +test_expect_success 'git checkout -p HEAD with NO staged changes: apply' ' + (echo n; echo y; echo y) | git checkout -p HEAD && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p HEAD with change already staged' ' + set_state dir/foo index index + # the third n is to get out in case it mistakenly does not apply + (echo n; echo y; echo n) | git checkout -p HEAD && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p HEAD^' ' + # the third n is to get out in case it mistakenly does not apply + (echo n; echo y; echo n) | git checkout -p HEAD^ && + verify_saved_state bar && + verify_state dir/foo parent parent +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'path limiting works: dir' ' + set_state dir/foo work head && + (echo y; echo n) | git checkout -p dir && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'path limiting works: -- dir' ' + set_state dir/foo work head && + (echo y; echo n) | git checkout -p -- dir && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'path limiting works: HEAD^ -- dir' ' + # the third n is to get out in case it mistakenly does not apply + (echo y; echo n; echo n) | git checkout -p HEAD^ -- dir && + verify_saved_state bar && + verify_state dir/foo parent parent +' + +test_expect_success 'path limiting works: foo inside dir' ' + set_state dir/foo work head && + # the third n is to get out in case it mistakenly does not apply + (echo y; echo n; echo n) | (cd dir && git checkout -p foo) && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + +test_done diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7a3fb67..5514f74 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,4 +200,23 @@ test_expect_success 'drop -q is quiet' ' test ! -s output.out ' +test_expect_success 'stash -k' ' + echo bar3 > file && + echo bar4 > file2 && + git add file2 && + git stash -k && + test bar,bar4 = $(cat file),$(cat file2) +' + +test_expect_success 'stash --invalid-option' ' + echo bar5 > file && + echo bar6 > file2 && + git add file2 && + test_must_fail git stash --invalid-option && + test_must_fail git stash save --invalid-option && + test bar5,bar6 = $(cat file),$(cat file2) && + git stash -- -message-starting-with-dash && + test bar,bar2 = $(cat file),$(cat file2) +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh new file mode 100755 index 0000000..f37e3bc --- /dev/null +++ b/t/t3904-stash-patch.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='git checkout --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add bar dir/foo && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + echo index > dir/foo && + git add dir/foo && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before dir, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_state dir/foo work index + (echo n; echo n) | test_must_fail git stash save -p && + verify_state dir/foo work index && + verify_saved_state bar +' + +test_expect_success 'git stash -p' ' + (echo n; echo y) | git stash save -p && + verify_state dir/foo head index && + verify_saved_state bar && + git reset --hard && + git stash apply && + verify_state dir/foo work head && + verify_state bar dummy dummy +' + +test_expect_success 'git stash -p --no-keep-index' ' + set_state dir/foo work index && + set_state bar bar_work bar_index && + (echo n; echo y) | git stash save -p --no-keep-index && + verify_state dir/foo head head && + verify_state bar bar_work dummy && + git reset --hard && + git stash apply --index && + verify_state dir/foo work index && + verify_state bar dummy bar_index +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + +test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index e70ea94..0279d07 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,18 +11,26 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 13' + test `cat last` = 14' + +check_mailinfo () { + mail=$1 opt=$2 + mo="$mail$opt" + git mailinfo -u $opt msg$mo patch$mo <$mail >info$mo && + test_cmp "$TEST_DIRECTORY"/t5100/msg$mo msg$mo && + test_cmp "$TEST_DIRECTORY"/t5100/patch$mo patch$mo && + test_cmp "$TEST_DIRECTORY"/t5100/info$mo info$mo +} + for mail in `echo 00*` do test_expect_success "mailinfo $mail" ' - git mailinfo -u msg$mail patch$mail <$mail >info$mail && - echo msg && - test_cmp "$TEST_DIRECTORY"/t5100/msg$mail msg$mail && - echo patch && - test_cmp "$TEST_DIRECTORY"/t5100/patch$mail patch$mail && - echo info && - test_cmp "$TEST_DIRECTORY"/t5100/info$mail info$mail + check_mailinfo $mail "" && + if test -f "$TEST_DIRECTORY"/t5100/msg$mail--scissors + then + check_mailinfo $mail --scissors + fi ' done diff --git a/t/t5100/info0014 b/t/t5100/info0014 new file mode 100644 index 0000000..08566b3 --- /dev/null +++ b/t/t5100/info0014 @@ -0,0 +1,5 @@ +Author: Junio Hamano +Email: junkio@cox.net +Subject: BLAH ONE +Date: Thu, 20 Aug 2009 17:18:22 -0700 + diff --git a/t/t5100/info0014--scissors b/t/t5100/info0014--scissors new file mode 100644 index 0000000..ab9c8d0 --- /dev/null +++ b/t/t5100/info0014--scissors @@ -0,0 +1,5 @@ +Author: Junio C Hamano +Email: gitster@pobox.com +Subject: Teach mailinfo to ignore everything before -- >8 -- mark +Date: Thu, 20 Aug 2009 17:18:22 -0700 + diff --git a/t/t5100/msg0014 b/t/t5100/msg0014 new file mode 100644 index 0000000..62e5cd2 --- /dev/null +++ b/t/t5100/msg0014 @@ -0,0 +1,18 @@ +In real life, we will see a discussion that inspired this patch +discussing related and unrelated things around >8 scissors mark +in this part of the message. + +Subject: [PATCH] BLAH TWO + +And then we will see the scissors. + + This line is not a scissors mark -- >8 -- but talks about it. + - - >8 - - please remove everything above this line - - >8 - - + +Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark +From: Junio C Hamano <gitster@pobox.com> + +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/t/t5100/msg0014--scissors b/t/t5100/msg0014--scissors new file mode 100644 index 0000000..259c6a4 --- /dev/null +++ b/t/t5100/msg0014--scissors @@ -0,0 +1,4 @@ +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/t/t5100/patch0014 b/t/t5100/patch0014 new file mode 100644 index 0000000..124efd2 --- /dev/null +++ b/t/t5100/patch0014 @@ -0,0 +1,64 @@ +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5100/patch0014--scissors b/t/t5100/patch0014--scissors new file mode 100644 index 0000000..124efd2 --- /dev/null +++ b/t/t5100/patch0014--scissors @@ -0,0 +1,64 @@ +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index c3074ac..13fa4ae 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -561,3 +561,92 @@ From: <a.u.thor@example.com> (A U Thor) Date: Fri, 9 Jun 2006 00:44:16 -0700 Subject: [PATCH] a patch +From nobody Mon Sep 17 00:00:00 2001 +From: Junio Hamano <junkio@cox.net> +Date: Thu, 20 Aug 2009 17:18:22 -0700 +Subject: Why doesn't git-am does not like >8 scissors mark? + +Subject: [PATCH] BLAH ONE + +In real life, we will see a discussion that inspired this patch +discussing related and unrelated things around >8 scissors mark +in this part of the message. + +Subject: [PATCH] BLAH TWO + +And then we will see the scissors. + + This line is not a scissors mark -- >8 -- but talks about it. + - - >8 - - please remove everything above this line - - >8 - - + +Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark +From: Junio C Hamano <gitster@pobox.com> + +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5501-post-upload-pack.sh b/t/t5501-post-upload-pack.sh new file mode 100755 index 0000000..d89fb51 --- /dev/null +++ b/t/t5501-post-upload-pack.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='post upload-hook' + +. ./test-lib.sh + +LOGFILE=".git/post-upload-pack-log" + +test_expect_success setup ' + test_commit A && + test_commit B && + git reset --hard A && + test_commit C && + git branch prev B && + mkdir -p .git/hooks && + { + echo "#!$SHELL_PATH" && + echo "cat >post-upload-pack-log" + } >".git/hooks/post-upload-pack" && + chmod +x .git/hooks/post-upload-pack +' + +test_expect_success initial ' + rm -fr sub && + git init sub && + ( + cd sub && + git fetch --no-tags .. prev + ) && + want=$(sed -n "s/^want //p" "$LOGFILE") && + test "$want" = "$(git rev-parse --verify B)" && + ! grep "^have " "$LOGFILE" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = fetch +' + +test_expect_success second ' + rm -fr sub && + git init sub && + ( + cd sub && + git fetch --no-tags .. prev:refs/remotes/prev && + git fetch --no-tags .. master + ) && + want=$(sed -n "s/^want //p" "$LOGFILE") && + test "$want" = "$(git rev-parse --verify C)" && + have=$(sed -n "s/^have //p" "$LOGFILE") && + test "$have" = "$(git rev-parse --verify B)" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = fetch +' + +test_expect_success all ' + rm -fr sub && + HERE=$(pwd) && + git init sub && + ( + cd sub && + git clone "file://$HERE/.git" new + ) && + sed -n "s/^want //p" "$LOGFILE" | sort >actual && + git rev-parse A B C | sort >expect && + test_cmp expect actual && + ! grep "^have " "$LOGFILE" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = clone +' + +test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f..6889a53 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -122,6 +122,23 @@ test_expect_success 'fetch with insteadOf' ' ) ' +test_expect_success 'fetch with pushInsteadOf (should not rewrite)' ' + mk_empty && + ( + TRASH=$(pwd)/ && + cd testrepo && + git config "url.trash/.pushInsteadOf" "$TRASH" && + git config remote.up.url "$TRASH." && + git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && + git fetch up && + + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push without wildcard' ' mk_empty && @@ -162,6 +179,36 @@ test_expect_success 'push with insteadOf' ' ) ' +test_expect_success 'push with pushInsteadOf' ' + mk_empty && + TRASH="$(pwd)/" && + git config "url.$TRASH.pushInsteadOf" trash/ && + git push trash/testrepo refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + +test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' ' + mk_empty && + TRASH="$(pwd)/" && + git config "url.trash2/.pushInsteadOf" trash/ && + git config remote.r.url trash/wrong && + git config remote.r.pushurl "$TRASH/testrepo" && + git push r refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push with matching heads' ' mk_test heads/master && diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 2335d8b..2147567 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -149,11 +149,13 @@ test_expect_success 'clone a void' ' ( cd src-0 && git init ) && - git clone src-0 target-6 && + git clone "file://$(pwd)/src-0" target-6 2>err-6 && + ! grep "fatal:" err-6 && ( cd src-0 && test_commit A ) && - git clone src-0 target-7 && + git clone "file://$(pwd)/src-0" target-7 2>err-7 && + ! grep "fatal:" err-7 && # There is no reason to insist they are bit-for-bit # identical, but this test should suffice for now. test_cmp target-6/.git/config target-7/.git/config diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh new file mode 100755 index 0000000..f3f9a76 --- /dev/null +++ b/t/t5706-clone-branch.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +test_description='clone --branch option' +. ./test-lib.sh + +check_HEAD() { + echo refs/heads/"$1" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +} + +check_file() { + echo "$1" >expect && + test_cmp expect file +} + +test_expect_success 'setup' ' + mkdir parent && + (cd parent && git init && + echo one >file && git add file && git commit -m one && + git checkout -b two && + echo two >file && git add file && git commit -m two && + git checkout master) +' + +test_expect_success 'vanilla clone chooses HEAD' ' + git clone parent clone && + (cd clone && + check_HEAD master && + check_file one + ) +' + +test_expect_success 'clone -b chooses specified branch' ' + git clone -b two parent clone-two && + (cd clone-two && + check_HEAD two && + check_file two + ) +' + +test_expect_success 'clone -b sets up tracking' ' + (cd clone-two && + echo origin >expect && + git config branch.two.remote >actual && + echo refs/heads/two >>expect && + git config branch.two.merge >>actual && + test_cmp expect actual + ) +' + +test_expect_success 'clone -b does not munge remotes/origin/HEAD' ' + (cd clone-two && + echo refs/remotes/origin/master >expect && + git symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'clone -b with bogus branch chooses HEAD' ' + git clone -b bogus parent clone-bogus && + (cd clone-bogus && + check_HEAD master && + check_file one + ) +' + +test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index b4709e2..ae56a36 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -328,4 +328,21 @@ test_expect_success 'grep -p -B5' ' test_cmp expected actual ' +test_expect_success 'grep from a subdirectory to search wider area (1)' ' + mkdir -p s && + ( + cd s && git grep "x x x" .. + ) +' + +test_expect_success 'grep from a subdirectory to search wider area (2)' ' + mkdir -p s && + ( + cd s || exit 1 + ( git grep xxyyzz .. >out ; echo $? >status ) + ! test -s out && + test 1 = $(cat status) + ) +' + test_done diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh new file mode 100755 index 0000000..c1f4fc3 --- /dev/null +++ b/t/t7105-reset-patch.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git reset --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add dir && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work work + (echo n; echo n) | git reset -p && + verify_saved_state dir/foo && + verify_saved_state bar +' + +test_expect_success 'git reset -p' ' + (echo n; echo y) | git reset -p && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^' ' + (echo n; echo y) | git reset -p HEAD^ && + verify_state dir/foo work parent && + verify_saved_state bar +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'git reset -p dir' ' + set_state dir/foo work work + (echo y; echo n) | git reset -p dir && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p -- foo (inside dir)' ' + set_state dir/foo work work + (echo y; echo n) | (cd dir && git reset -p -- foo) && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^ -- dir' ' + (echo y; echo n) | git reset -p HEAD^ -- dir && + verify_state dir/foo work parent && + verify_saved_state bar +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + + +test_done diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index a4b00f2..83cc5fc 100755 --- a/t/t9138-git-svn-authors-prog.sh +++ b/t/t9138-git-svn-authors-prog.sh @@ -66,4 +66,18 @@ test_expect_success 'authors-file overrode authors-prog' ' ) ' +git --git-dir=x/.git config --unset svn.authorsfile +git --git-dir=x/.git config --unset svn.authorsprog + +test_expect_success 'authors-prog handled special characters in username' ' + svn mkdir -m bad --username "xyz; touch evil" "$svnrepo"/bad && + ( + cd x && + git svn --authors-prog=../svn-authors-prog fetch && + git rev-list -1 --pretty=raw refs/remotes/git-svn | + grep "^author xyz; touch evil <xyz; touch evil@example\.com> " && + ! test -f evil + ) +' + test_done diff --git a/transport-helper.c b/transport-helper.c new file mode 100644 index 0000000..f57e84c --- /dev/null +++ b/transport-helper.c @@ -0,0 +1,168 @@ +#include "cache.h" +#include "transport.h" + +#include "run-command.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" + +struct helper_data +{ + const char *name; + struct child_process *helper; + unsigned fetch : 1; +}; + +static struct child_process *get_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + struct child_process *helper; + FILE *file; + + if (data->helper) + return data->helper; + + helper = xcalloc(1, sizeof(*helper)); + helper->in = -1; + helper->out = -1; + helper->err = 0; + helper->argv = xcalloc(4, sizeof(*helper->argv)); + strbuf_addf(&buf, "remote-%s", data->name); + helper->argv[0] = strbuf_detach(&buf, NULL); + helper->argv[1] = transport->remote->name; + helper->argv[2] = transport->url; + helper->git_cmd = 1; + if (start_command(helper)) + die("Unable to run helper: git %s", helper->argv[0]); + data->helper = helper; + + write_str_in_full(helper->in, "capabilities\n"); + + file = xfdopen(helper->out, "r"); + while (1) { + if (strbuf_getline(&buf, file, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!*buf.buf) + break; + if (!strcmp(buf.buf, "fetch")) + data->fetch = 1; + } + return data->helper; +} + +static int disconnect_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + if (data->helper) { + write_str_in_full(data->helper->in, "\n"); + close(data->helper->in); + finish_command(data->helper); + free((char *)data->helper->argv[0]); + free(data->helper->argv); + free(data->helper); + data->helper = NULL; + } + return 0; +} + +static int fetch_with_fetch(struct transport *transport, + int nr_heads, const struct ref **to_fetch) +{ + struct child_process *helper = get_helper(transport); + FILE *file = xfdopen(helper->out, "r"); + int i; + struct strbuf buf = STRBUF_INIT; + + for (i = 0; i < nr_heads; i++) { + const struct ref *posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + + strbuf_addf(&buf, "fetch %s %s\n", + sha1_to_hex(posn->old_sha1), posn->name); + write_in_full(helper->in, buf.buf, buf.len); + strbuf_reset(&buf); + + if (strbuf_getline(&buf, file, '\n') == EOF) + exit(128); /* child died, message supplied already */ + } + return 0; +} + +static int fetch(struct transport *transport, + int nr_heads, const struct ref **to_fetch) +{ + struct helper_data *data = transport->data; + int i, count; + + count = 0; + for (i = 0; i < nr_heads; i++) + if (!(to_fetch[i]->status & REF_STATUS_UPTODATE)) + count++; + + if (!count) + return 0; + + if (data->fetch) + return fetch_with_fetch(transport, nr_heads, to_fetch); + + return -1; +} + +static struct ref *get_refs_list(struct transport *transport, int for_push) +{ + struct child_process *helper; + struct ref *ret = NULL; + struct ref **tail = &ret; + struct ref *posn; + struct strbuf buf = STRBUF_INIT; + FILE *file; + + helper = get_helper(transport); + + write_str_in_full(helper->in, "list\n"); + + file = xfdopen(helper->out, "r"); + while (1) { + char *eov, *eon; + if (strbuf_getline(&buf, file, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!*buf.buf) + break; + + eov = strchr(buf.buf, ' '); + if (!eov) + die("Malformed response in ref list: %s", buf.buf); + eon = strchr(eov + 1, ' '); + *eov = '\0'; + if (eon) + *eon = '\0'; + *tail = alloc_ref(eov + 1); + if (buf.buf[0] == '@') + (*tail)->symref = xstrdup(buf.buf + 1); + else if (buf.buf[0] != '?') + get_sha1_hex(buf.buf, (*tail)->old_sha1); + tail = &((*tail)->next); + } + strbuf_release(&buf); + + for (posn = ret; posn; posn = posn->next) + resolve_remote_symref(posn, ret); + + return ret; +} + +int transport_helper_init(struct transport *transport, const char *name) +{ + struct helper_data *data = xcalloc(sizeof(*data), 1); + data->name = name; + + transport->data = data; + transport->get_refs_list = get_refs_list; + transport->fetch = fetch; + transport->disconnect = disconnect_helper; + return 0; +} diff --git a/transport.c b/transport.c index f7e1663..4cb8077 100644 --- a/transport.c +++ b/transport.c @@ -1,9 +1,6 @@ #include "cache.h" #include "transport.h" #include "run-command.h" -#ifndef NO_CURL -#include "http.h" -#endif #include "pkt-line.h" #include "fetch-pack.h" #include "send-pack.h" @@ -352,45 +349,6 @@ static int rsync_transport_push(struct transport *transport, return result; } -/* Generic functions for using commit walkers */ - -#ifndef NO_CURL /* http fetch is the only user */ -static int fetch_objs_via_walker(struct transport *transport, - int nr_objs, const struct ref **to_fetch) -{ - char *dest = xstrdup(transport->url); - struct walker *walker = transport->data; - char **objs = xmalloc(nr_objs * sizeof(*objs)); - int i; - - walker->get_all = 1; - walker->get_tree = 1; - walker->get_history = 1; - walker->get_verbosely = transport->verbose >= 0; - walker->get_recover = 0; - - for (i = 0; i < nr_objs; i++) - objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); - - if (walker_fetch(walker, nr_objs, objs, NULL, NULL)) - die("Fetch failed."); - - for (i = 0; i < nr_objs; i++) - free(objs[i]); - free(objs); - free(dest); - return 0; -} -#endif /* NO_CURL */ - -static int disconnect_walker(struct transport *transport) -{ - struct walker *walker = transport->data; - if (walker) - walker_free(walker); - return 0; -} - #ifndef NO_CURL static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) { @@ -418,96 +376,6 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons return !!run_command_v_opt(argv, RUN_GIT_CMD); } -static struct ref *get_refs_via_curl(struct transport *transport, int for_push) -{ - struct strbuf buffer = STRBUF_INIT; - char *data, *start, *mid; - char *ref_name; - char *refs_url; - int i = 0; - int http_ret; - - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; - - struct walker *walker; - - if (for_push) - return NULL; - - if (!transport->data) - transport->data = get_http_walker(transport->url, - transport->remote); - - walker = transport->data; - - refs_url = xmalloc(strlen(transport->url) + 11); - sprintf(refs_url, "%s/info/refs", transport->url); - - http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); - switch (http_ret) { - case HTTP_OK: - break; - case HTTP_MISSING_TARGET: - die("%s not found: did you run git update-server-info on the" - " server?", refs_url); - default: - http_error(refs_url, http_ret); - die("HTTP request failed"); - } - - data = buffer.buf; - start = NULL; - mid = data; - while (i < buffer.len) { - if (!start) - start = &data[i]; - if (data[i] == '\t') - mid = &data[i]; - if (data[i] == '\n') { - data[i] = 0; - ref_name = mid + 1; - ref = xmalloc(sizeof(struct ref) + - strlen(ref_name) + 1); - memset(ref, 0, sizeof(struct ref)); - strcpy(ref->name, ref_name); - get_sha1_hex(start, ref->old_sha1); - if (!refs) - refs = ref; - if (last_ref) - last_ref->next = ref; - last_ref = ref; - start = NULL; - } - i++; - } - - strbuf_release(&buffer); - - ref = alloc_ref("HEAD"); - if (!walker->fetch_ref(walker, ref) && - !resolve_remote_symref(ref, refs)) { - ref->next = refs; - refs = ref; - } else { - free(ref); - } - - strbuf_release(&buffer); - free(refs_url); - return refs; -} - -static int fetch_objs_via_curl(struct transport *transport, - int nr_objs, const struct ref **to_fetch) -{ - if (!transport->data) - transport->data = get_http_walker(transport->url, - transport->remote); - return fetch_objs_via_walker(transport, nr_objs, to_fetch); -} - #endif struct bundle_transport_data { @@ -955,14 +823,12 @@ struct transport *transport_get(struct remote *remote, const char *url) } else if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://") || !prefixcmp(url, "ftp://")) { + transport_helper_init(ret, "curl"); #ifdef NO_CURL error("git was compiled without libcurl support."); #else - ret->get_refs_list = get_refs_via_curl; - ret->fetch = fetch_objs_via_curl; ret->push = curl_transport_push; #endif - ret->disconnect = disconnect_walker; } else if (is_local(url) && is_file(url)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); @@ -1042,7 +908,7 @@ int transport_push(struct transport *transport, update_tracking_ref(transport->remote, ref, verbose); } - if (!ret && !refs_pushed(remote_refs)) + if (!quiet && !ret && !refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); return ret; } diff --git a/transport.h b/transport.h index 171a01c..c14da6f 100644 --- a/transport.h +++ b/transport.h @@ -79,4 +79,7 @@ void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); +/* Transport methods defined outside transport.c */ +int transport_helper_init(struct transport *transport, const char *name); + #endif diff --git a/upload-pack.c b/upload-pack.c index dacbc76..38ddac2 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -32,6 +32,7 @@ static int no_progress, daemon_mode; static int shallow_nr; static struct object_array have_obj; static struct object_array want_obj; +static struct object_array extra_edge_obj; static unsigned int timeout; /* 0 for no sideband, * otherwise maximum packet size (up to 65520 bytes). @@ -107,7 +108,7 @@ static int do_rev_list(int fd, void *create_full_pack) int i; struct rev_info revs; - pack_pipe = fdopen(fd, "w"); + pack_pipe = xfdopen(fd, "w"); init_revisions(&revs, NULL); revs.tag_objects = 1; revs.tree_objects = 1; @@ -135,14 +136,76 @@ static int do_rev_list(int fd, void *create_full_pack) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); + if (use_thin_pack) + for (i = 0; i < extra_edge_obj.nr; i++) + fprintf(pack_pipe, "-%s\n", sha1_to_hex( + extra_edge_obj.objects[i].item->sha1)); traverse_commit_list(&revs, show_commit, show_object, NULL); fflush(pack_pipe); fclose(pack_pipe); return 0; } +static int feed_msg_to_hook(int fd, const char *fmt, ...) +{ + int cnt; + char buf[1024]; + va_list params; + + va_start(params, fmt); + cnt = vsprintf(buf, fmt, params); + va_end(params); + return write_in_full(fd, buf, cnt) != cnt; +} + +static int feed_obj_to_hook(const char *label, struct object_array *oa, int i, int fd) +{ + return feed_msg_to_hook(fd, "%s %s\n", label, + sha1_to_hex(oa->objects[i].item->sha1)); +} + +static int run_post_upload_pack_hook(size_t total, struct timeval *tv) +{ + const char *argv[2]; + struct child_process proc; + int err, i; + + argv[0] = "hooks/post-upload-pack"; + argv[1] = NULL; + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&proc, 0, sizeof(proc)); + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + err = start_command(&proc); + if (err) + return err; + for (i = 0; !err && i < want_obj.nr; i++) + err |= feed_obj_to_hook("want", &want_obj, i, proc.in); + for (i = 0; !err && i < have_obj.nr; i++) + err |= feed_obj_to_hook("have", &have_obj, i, proc.in); + if (!err) + err |= feed_msg_to_hook(proc.in, "time %ld.%06ld\n", + (long)tv->tv_sec, (long)tv->tv_usec); + if (!err) + err |= feed_msg_to_hook(proc.in, "size %ld\n", (long)total); + if (!err) + err |= feed_msg_to_hook(proc.in, "kind %s\n", + (nr_our_refs == want_obj.nr && !have_obj.nr) + ? "clone" : "fetch"); + if (close(proc.in)) + err = 1; + if (finish_command(&proc)) + err = 1; + return err; +} + static void create_pack_file(void) { + struct timeval start_tv, tv; struct async rev_list; struct child_process pack_objects; int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); @@ -150,10 +213,12 @@ static void create_pack_file(void) char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; int buffered = -1; - ssize_t sz; + ssize_t sz, total_sz; const char *argv[10]; int arg = 0; + gettimeofday(&start_tv, NULL); + total_sz = 0; if (shallow_nr) { rev_list.proc = do_rev_list; rev_list.data = 0; @@ -190,7 +255,7 @@ static void create_pack_file(void) /* pass on revisions we (don't) want */ if (!shallow_nr) { - FILE *pipe_fd = fdopen(pack_objects.in, "w"); + FILE *pipe_fd = xfdopen(pack_objects.in, "w"); if (!create_full_pack) { int i; for (i = 0; i < want_obj.nr; i++) @@ -262,7 +327,7 @@ static void create_pack_file(void) sz = xread(pack_objects.out, cp, sizeof(data) - outsz); if (0 < sz) - ; + total_sz += sz; else if (sz == 0) { close(pack_objects.out); pack_objects.out = -1; @@ -314,6 +379,16 @@ static void create_pack_file(void) } if (use_sideband) packet_flush(1); + + gettimeofday(&tv, NULL); + tv.tv_sec -= start_tv.tv_sec; + if (tv.tv_usec < start_tv.tv_usec) { + tv.tv_sec--; + tv.tv_usec += 1000000; + } + tv.tv_usec -= start_tv.tv_usec; + if (run_post_upload_pack_hook(total_sz, &tv)) + warning("post-upload-hook failed"); return; fail: @@ -478,7 +553,7 @@ static void receive_needs(void) shallow_nr = 0; if (debug_fd) - write_in_full(debug_fd, "#S\n", 3); + write_str_in_full(debug_fd, "#S\n"); for (;;) { struct object *o; unsigned char sha1_buf[20]; @@ -492,7 +567,6 @@ static void receive_needs(void) if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; - use_thin_pack = 0; if (get_sha1(line + 8, sha1)) die("invalid shallow line: %s", line); object = parse_object(sha1); @@ -504,7 +578,6 @@ static void receive_needs(void) } if (!prefixcmp(line, "deepen ")) { char *end; - use_thin_pack = 0; depth = strtol(line + 7, &end, 0); if (end == line + 7 || depth <= 0) die("Invalid deepen: %s", line); @@ -546,7 +619,7 @@ static void receive_needs(void) } } if (debug_fd) - write_in_full(debug_fd, "#E\n", 3); + write_str_in_full(debug_fd, "#E\n"); if (!use_sideband && daemon_mode) no_progress = 1; @@ -587,6 +660,7 @@ static void receive_needs(void) NULL, &want_obj); parents = parents->next; } + add_object_array(object, NULL, &extra_edge_obj); } /* make sure commit traversal conforms to client */ register_shallow(object->sha1); diff --git a/wt-status.c b/wt-status.c index 3395456..38eb245 100644 --- a/wt-status.c +++ b/wt-status.c @@ -48,6 +48,8 @@ static void wt_status_print_unmerged_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Unmerged paths:"); + if (!advice_status_hints) + return; if (!s->is_initial) color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); else @@ -60,6 +62,8 @@ static void wt_status_print_cached_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + if (!advice_status_hints) + return; if (!s->is_initial) { color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { @@ -73,6 +77,8 @@ static void wt_status_print_dirty_header(struct wt_status *s, { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changed but not updated:"); + if (!advice_status_hints) + return; if (!has_deleted) color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)"); else @@ -85,6 +91,8 @@ static void wt_status_print_untracked_header(struct wt_status *s) { const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Untracked files:"); + if (!advice_status_hints) + return; color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); color_fprintf_ln(s->fp, c, "#"); } @@ -561,8 +569,8 @@ void wt_status_print(struct wt_status *s) color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } - wt_status_print_unmerged(s); wt_status_print_updated(s); + wt_status_print_unmerged(s); wt_status_print_changed(s); if (s->submodule_summary) wt_status_print_submodule_summary(s); |