diff options
263 files changed, 6286 insertions, 1961 deletions
diff --git a/.gitignore b/.gitignore index acffdfaae..8572c8c0b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /GIT-BUILD-OPTIONS /GIT-CFLAGS +/GIT-LDFLAGS /GIT-GUI-VARS /GIT-VERSION-FILE /bin-wrappers/ diff --git a/Documentation/.gitignore b/Documentation/.gitignore index 1c3a9fead..d62aebd84 100644 --- a/Documentation/.gitignore +++ b/Documentation/.gitignore @@ -3,6 +3,7 @@ *.[1-8] *.made *.texi +*.pdf git.info gitman.info howto-index.txt diff --git a/Documentation/Makefile b/Documentation/Makefile index 36989b7f6..18c71d763 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -232,6 +232,7 @@ cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT) clean: $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 $(RM) *.texi *.texi+ *.texi++ git.info gitman.info + $(RM) *.pdf $(RM) howto-index.txt howto/*.html doc.dep $(RM) technical/api-*.html technical/api-index.txt $(RM) $(cmds_txt) *.made diff --git a/Documentation/RelNotes/1.7.7.txt b/Documentation/RelNotes/1.7.7.txt new file mode 100644 index 000000000..6d3bfd196 --- /dev/null +++ b/Documentation/RelNotes/1.7.7.txt @@ -0,0 +1,98 @@ +Git v1.7.7 Release Notes +======================== + +Updates since v1.7.6 +-------------------- + + * The scripting part of the codebase is getting prepared for i18n/l10n. + + * Interix and Cygwin ports got updated. + + * Various codepaths that invoked zlib deflate/inflate assumed that these + functions can compress or uncompress more than 4GB data in one call on + platforms with 64-bit long, which has been corrected. + + * "git archive" can be told to pass the output to gzip compression and + produce "archive.tar.gz". + + * "git checkout" (both the code to update the files upon checking out a + different branch, the code to checkout specific set of files) learned + to stream the data from object store when possible, without having to + read the entire contents of a file in memory first. + + * "git clone" can now take "--config key=value" option to set the + repository configuration options that affect the initial checkout. + + * "git diff --stat" learned --stat-count option to limit the output of + diffstat report. + + * "git fetch", "git push" and friends no longer show connection + errors for addresses that couldn't be connected when at least one + address succeeds (this is arguably a regression but a deliberate + one). + + * "git grep" learned --break and --heading options, to let users mimic + output format of "ack". + + * "git rebase master topci" no longer spews usage hints after giving + "fatal: no such branch: topci" error message. + + * "git stash" learned --include-untracked option. + + * "git submodule update" used to stop at the first error updating a + submodule; it now goes on to update other submodules that can be + updated, and reports the ones with errors at the end. + + * "git verify-pack" has been rewritten to use the "index-pack" machinery + that is more efficient in reading objects in packfiles. + + * test scripts for gitweb tried to run even when CGI-related perl modules + are not installed; it now exits early when they are unavailable. + +Also contains various documentation updates and minor miscellaneous +changes. + + +Fixes since v1.7.6 +------------------ + +Unless otherwise noted, all the fixes in 1.7.6.X maintenance track are +included in this release. + + * "git checkout -b <branch>" sometimes wrote a bogus reflog entry, + causing later "git checkout -" fail. + (merge 71ee7fd jc/checkout-reflog-fix~1 later). + + * "git diff --cc" learned to correctly ignore binary files. + (merge 0508fe5 jk/combine-diff-binary-etc later) + + * "git fetch" did not recurse into submodules in subdirectories. + (merge ea2d325 jl/maint-fetch-recursive-fix later) + + * "git rebase -i -p" incorrectly dropped commits from side branches. + (merge 12bf828 aw/rebase-i-p later) + + * "git submodule add" did not allow a relative repository path when + the superproject did not have any default remote url. + (merge f22a17e8 jl/submodule-add-relurl-wo-upstream later) + + * "git submodule foreach" failed to correctly give the standard input to + the user-supplied command it invoked. + (merge 4dca1aa bc/submodule-foreach-stdin-fix-1.7.4 later) + + * submodules that the user has never showed interest in by running + "git submodule init" was incorrectly marked as interesting by "git + submodule sync". + (merge 2cd9de3 jc/submodule-sync-no-auto-vivify later) + + * "git tag -l <glob>..." did not take multiple glob patterns from the + command line. + (merge 588d0e8 jk/tag-list-multiple-patterns later) + +-- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.7.6-344-g22f4128 +git log --first-parent --oneline $O..master +echo +git shortlog --no-merges ^maint ^$O master diff --git a/Documentation/config.txt b/Documentation/config.txt index 6b9377719..0658ffb88 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -344,7 +344,9 @@ core.logAllRefUpdates:: SHA1, the date/time and the reason of the update, but only when the file exists. If this configuration variable is set to true, missing "$GIT_DIR/logs/<ref>" - file is automatically created for branch heads. + file is automatically created for branch heads (i.e. under + refs/heads/), remote refs (i.e. under refs/remotes/), + note refs (i.e. under refs/notes/), and the symbolic ref HEAD. + This information can be used to determine what commit was the tip of a branch "2 days ago". @@ -676,7 +678,7 @@ branch.<name>.rebase:: browser.<tool>.cmd:: Specify the command to invoke the specified browser. The specified command is evaluated in shell with the URLs passed - as arguments. (See linkgit:git-web--browse[1].) + as arguments. (See linkgit:git-web{litdd}browse[1].) browser.<tool>.path:: Override the path for the given tool that may be used to @@ -1196,6 +1198,14 @@ http.proxy:: environment variable (see linkgit:curl[1]). This can be overridden on a per-remote basis; see remote.<name>.proxy +http.cookiefile:: + File containing previously stored cookie lines which should be used + in the git http session, if they match the server. The file format + of the file to read cookies from should be plain HTTP headers or + the Netscape/Mozilla cookie file format (see linkgit:curl[1]). + NOTE that the file specified with http.cookiefile is only used as + input. No cookies will be stored in the file. + http.sslVerify:: Whether to verify the SSL certificate when fetching or pushing over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 659de6f12..b620b3afe 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -48,11 +48,17 @@ endif::git-format-patch[] --patience:: Generate a diff using the "patience diff" algorithm. ---stat[=<width>[,<name-width>]]:: +--stat[=<width>[,<name-width>[,<count>]]]:: Generate a diffstat. You can override the default output width for 80-column terminal by `--stat=<width>`. The width of the filename part can be controlled by giving another width to it separated by a comma. + By giving a third parameter `<count>`, you can limit the + output to the first `<count>` lines, followed by + `...` if there are more. ++ +These parameters can also be set individually with `--stat-width=<width>`, +`--stat-name-width=<name-width>` and `--stat-count=<count>`. --numstat:: Similar to `\--stat`, but shows number of added and diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt index 9eb75c37d..05fd482b7 100644 --- a/Documentation/git-annotate.txt +++ b/Documentation/git-annotate.txt @@ -7,6 +7,7 @@ git-annotate - Annotate file lines with commit information SYNOPSIS -------- +[verse] 'git annotate' [options] file [revision] DESCRIPTION diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 9c750e244..ac7006e64 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -101,6 +101,25 @@ tar.umask:: details. If `--remote` is used then only the configuration of the remote repository takes effect. +tar.<format>.command:: + This variable specifies a shell command through which the tar + output generated by `git archive` should be piped. The command + is executed using the shell with the generated tar file on its + standard input, and should produce the final output on its + standard output. Any compression-level options will be passed + to the command (e.g., "-9"). An output file with the same + extension as `<format>` will be use this format if no other + format is given. ++ +The "tar.gz" and "tgz" formats are defined automatically and default to +`gzip -cn`. You may override them with custom commands. + +tar.<format>.remote:: + If true, enable `<format>` for use by remote clients via + linkgit:git-upload-archive[1]. Defaults to false for + user-defined formats, but true for the "tar.gz" and "tgz" + formats. + ATTRIBUTES ---------- @@ -123,32 +142,46 @@ while archiving any tree in your `$GIT_DIR/info/attributes` file. EXAMPLES -------- -git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -):: +`git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -)`:: Create a tar archive that contains the contents of the latest commit on the current branch, and extract it in the `/var/tmp/junk` directory. -git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz:: +`git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz`:: Create a compressed tarball for v1.4.0 release. -git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz:: +`git archive --format=tar.gz --prefix=git-1.4.0/ v1.4.0 >git-1.4.0.tar.gz`:: + + Same as above, but using the builtin tar.gz handling. + +`git archive --prefix=git-1.4.0/ -o git-1.4.0.tar.gz v1.4.0`:: + + Same as above, but the format is inferred from the output file. + +`git archive --format=tar --prefix=git-1.4.0/ v1.4.0{caret}\{tree\} | gzip >git-1.4.0.tar.gz`:: Create a compressed tarball for v1.4.0 release, but without a global extended pax header. -git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip:: +`git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs.zip`:: Put everything in the current head's Documentation/ directory into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'. -git archive -o latest.zip HEAD:: +`git archive -o latest.zip HEAD`:: Create a Zip archive that contains the contents of the latest commit on the current branch. Note that the output format is inferred by the extension of the output file. +`git config tar.tar.xz.command "xz -c"`:: + + Configure a "tar.xz" format for making LZMA-compressed tarfiles. + You can use it specifying `--format=tar.xz`, or by creating an + output file like `-o foo.tar.xz`. + SEE ALSO -------- diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 7b7bafba0..ab60a1847 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -8,6 +8,7 @@ git-bisect - Find by binary search the change that introduced a bug SYNOPSIS -------- +[verse] 'git bisect' <subcommand> <options> DESCRIPTION diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 205d83dd0..c9fdf84a0 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -18,9 +18,12 @@ Checks if a given 'refname' is acceptable, and exits with a non-zero status if it is not. A reference is used in git to specify branches and tags. A -branch head is stored under the `$GIT_DIR/refs/heads` directory, and -a tag is stored under the `$GIT_DIR/refs/tags` directory (or, if refs -are packed by `git gc`, as entries in the `$GIT_DIR/packed-refs` file). +branch head is stored in the `refs/heads` hierarchy, while +a tag is stored in the `refs/tags` hierarchy of the ref namespace +(typically in `$GIT_DIR/refs/heads` and `$GIT_DIR/refs/tags` +directories or, as entries in file `$GIT_DIR/packed-refs` +if refs are packed by `git gc`). + git imposes the following rules on how references are named: . They can include slash `/` for hierarchical (directory) diff --git a/Documentation/git-cherry-pick.txt b/Documentation/git-cherry-pick.txt index 9d8fe0d26..7cfa3d92a 100644 --- a/Documentation/git-cherry-pick.txt +++ b/Documentation/git-cherry-pick.txt @@ -7,6 +7,7 @@ git-cherry-pick - Apply the changes introduced by some existing commits SYNOPSIS -------- +[verse] 'git cherry-pick' [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>... DESCRIPTION @@ -111,31 +112,31 @@ effect to your index in a row. EXAMPLES -------- -git cherry-pick master:: +`git cherry-pick master`:: Apply the change introduced by the commit at the tip of the master branch and create a new commit with this change. -git cherry-pick ..master:: -git cherry-pick ^HEAD master:: +`git cherry-pick ..master`:: +`git cherry-pick ^HEAD master`:: Apply the changes introduced by all commits that are ancestors of master but not of HEAD to produce new commits. -git cherry-pick master{tilde}4 master{tilde}2:: +`git cherry-pick master{tilde}4 master{tilde}2`:: Apply the changes introduced by the fifth and third last commits pointed to by master and create 2 new commits with these changes. -git cherry-pick -n master~1 next:: +`git cherry-pick -n master~1 next`:: Apply to the working tree and the index the changes introduced by the second last commit pointed to by master and by the last commit pointed to by next, but do not create any commit with these changes. -git cherry-pick --ff ..next:: +`git cherry-pick --ff ..next`:: If history is linear and HEAD is an ancestor of next, update the working tree and advance the HEAD pointer to match next. @@ -143,7 +144,7 @@ git cherry-pick --ff ..next:: are in next but not HEAD to the current branch, creating a new commit for each new change. -git rev-list --reverse master \-- README | git cherry-pick -n --stdin:: +`git rev-list --reverse master \-- README | git cherry-pick -n --stdin`:: Apply the changes introduced by all commits on the master branch that touched README to the working tree and index, diff --git a/Documentation/git-cherry.txt b/Documentation/git-cherry.txt index 79448c505..f6c19c734 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -7,6 +7,7 @@ git-cherry - Find commits not merged upstream SYNOPSIS -------- +[verse] 'git cherry' [-v] [<upstream> [<head> [<limit>]]] DESCRIPTION diff --git a/Documentation/git-citool.txt b/Documentation/git-citool.txt index 6e5c8126f..c7a11c36c 100644 --- a/Documentation/git-citool.txt +++ b/Documentation/git-citool.txt @@ -7,6 +7,7 @@ git-citool - Graphical alternative to git-commit SYNOPSIS -------- +[verse] 'git citool' DESCRIPTION diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index b093e4549..4b8b26b75 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -159,6 +159,17 @@ objects from the source repository into a pack in the cloned repository. Specify the directory from which templates will be used; (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].) +--config <key>=<value>:: +-c <key>=<value>:: + Set a configuration variable in the newly-created repository; + this takes effect immediately after the repository is + initialized, but before the remote history is fetched or any + files checked out. The key is in the same format as expected by + linkgit:git-config[1] (e.g., `core.eol=true`). If multiple + values are given for the same key, each value will be written to + the config file. This makes it safe, for example, to add + additional fetch refspecs to the origin remote. + --depth <depth>:: Create a 'shallow' clone with a history truncated to the specified number of revisions. A shallow repository has a diff --git a/Documentation/git-commit-tree.txt b/Documentation/git-commit-tree.txt index f524d7601..0fdb82ee8 100644 --- a/Documentation/git-commit-tree.txt +++ b/Documentation/git-commit-tree.txt @@ -8,6 +8,7 @@ git-commit-tree - Create a new commit object SYNOPSIS -------- +[verse] 'git commit-tree' <tree> [(-p <parent commit>)...] < changelog DESCRIPTION diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 7951cb7b0..5cc84a139 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -284,7 +284,7 @@ When recording your own work, the contents of modified files in your working tree are temporarily stored to a staging area called the "index" with 'git add'. A file can be reverted back, only in the index but not in the working tree, -to that of the last commit with `git reset HEAD -- <file>`, +to that of the last commit with `git reset HEAD \-- <file>`, which effectively reverts 'git add' and prevents the changes to this file from participating in the next commit. After building the state to be committed incrementally with these commands, diff --git a/Documentation/git-count-objects.txt b/Documentation/git-count-objects.txt index a73933a93..23c80cea6 100644 --- a/Documentation/git-count-objects.txt +++ b/Documentation/git-count-objects.txt @@ -7,6 +7,7 @@ git-count-objects - Count unpacked number of objects and their disk consumption SYNOPSIS -------- +[verse] 'git count-objects' [-v] DESCRIPTION diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index ad93a3e84..7f79cec3f 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,6 +8,7 @@ git-cvsexportcommit - Export a single commit to a CVS checkout SYNOPSIS -------- +[verse] 'git cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-w cvsworkdir] [-W] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt index 8d481948b..906774f0f 100644 --- a/Documentation/git-diff-files.txt +++ b/Documentation/git-diff-files.txt @@ -8,6 +8,7 @@ git-diff-files - Compares files in the working tree and the index SYNOPSIS -------- +[verse] 'git diff-files' [-q] [-0|-1|-2|-3|-c|--cc] [<common diff options>] [<path>...] DESCRIPTION diff --git a/Documentation/git-diff-index.txt b/Documentation/git-diff-index.txt index 2ea22abca..c0b7c581a 100644 --- a/Documentation/git-diff-index.txt +++ b/Documentation/git-diff-index.txt @@ -8,6 +8,7 @@ git-diff-index - Compares content and mode of blobs between the index and reposi SYNOPSIS -------- +[verse] 'git diff-index' [-m] [--cached] [<common diff options>] <tree-ish> [<path>...] DESCRIPTION diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 590f410ab..a03515f1e 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -7,6 +7,7 @@ git-difftool - Show changes using common diff tools SYNOPSIS -------- +[verse] 'git difftool' [<options>] [<commit> [<commit>]] [--] [<path>...] DESCRIPTION diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index 781bd6edc..f37eada63 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -8,6 +8,7 @@ git-fast-export - Git data exporter SYNOPSIS -------- +[verse] 'git fast-export [options]' | 'git fast-import' DESCRIPTION @@ -82,6 +83,10 @@ marks the same across runs. allow that. So fake a tagger to be able to fast-import the output. +--use-done-feature:: + Start the stream with a 'feature done' stanza, and terminate + it with a 'done' command. + --no-data:: Skip output of blob objects and instead refer to blobs via their original SHA-1 hash. This is useful when rewriting the diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt index 3f5b9126b..296938888 100644 --- a/Documentation/git-fast-import.txt +++ b/Documentation/git-fast-import.txt @@ -8,6 +8,7 @@ git-fast-import - Backend for fast Git data importers SYNOPSIS -------- +[verse] frontend | 'git fast-import' [options] DESCRIPTION @@ -101,6 +102,12 @@ OPTIONS when the `cat-blob` command is encountered in the stream. The default behaviour is to write to `stdout`. +--done:: + Require a `done` command at the end of the stream. + This option might be useful for detecting errors that + cause the frontend to terminate before it has started to + write a stream. + --export-pack-edges=<file>:: After creating a packfile, print a line of data to <file> listing the filename of the packfile and the last @@ -330,6 +337,11 @@ and control the current import process. More detailed discussion standard output. This command is optional and is not needed to perform an import. +`done`:: + Marks the end of the stream. This command is optional + unless the `done` feature was requested using the + `--done` command line option or `feature done` command. + `cat-blob`:: Causes fast-import to print a blob in 'cat-file --batch' format to the file descriptor set with `--cat-blob-fd` or @@ -1020,6 +1032,11 @@ notes:: Versions of fast-import not supporting notes will exit with a message indicating so. +done:: + Error out if the stream ends without a 'done' command. + Without this feature, errors causing the frontend to end + abruptly at a convenient point in the stream can go + undetected. `option` ~~~~~~~~ @@ -1049,6 +1066,15 @@ not be passed as option: * cat-blob-fd * force +`done` +~~~~~~ +If the `done` feature is not in use, treated as if EOF was read. +This can be used to tell fast-import to finish early. + +If the `--done` command line option or `feature done` command is +in use, the `done` command is mandatory and marks the end of the +stream. + Crash Reports ------------- If fast-import is supplied invalid input it will terminate with a diff --git a/Documentation/git-fetch-pack.txt b/Documentation/git-fetch-pack.txt index 48d4bf6d6..ed1bdaacd 100644 --- a/Documentation/git-fetch-pack.txt +++ b/Documentation/git-fetch-pack.txt @@ -8,6 +8,7 @@ git-fetch-pack - Receive missing objects from another repository SYNOPSIS -------- +[verse] 'git fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...] DESCRIPTION diff --git a/Documentation/git-fetch.txt b/Documentation/git-fetch.txt index 60ac8d26e..b41d7c1de 100644 --- a/Documentation/git-fetch.txt +++ b/Documentation/git-fetch.txt @@ -8,12 +8,10 @@ git-fetch - Download objects and refs from another repository SYNOPSIS -------- +[verse] 'git fetch' [<options>] [<repository> [<refspec>...]] - 'git fetch' [<options>] <group> - 'git fetch' --multiple [<options>] [(<repository> | <group>)...] - 'git fetch' --all [<options>] diff --git a/Documentation/git-fsck-objects.txt b/Documentation/git-fsck-objects.txt index 90ebb8a59..eec4bdb60 100644 --- a/Documentation/git-fsck-objects.txt +++ b/Documentation/git-fsck-objects.txt @@ -8,6 +8,7 @@ git-fsck-objects - Verifies the connectivity and validity of the objects in the SYNOPSIS -------- +[verse] 'git fsck-objects' ... DESCRIPTION diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 4966cb578..815afcb92 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -8,6 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository SYNOPSIS -------- +[verse] 'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] DESCRIPTION diff --git a/Documentation/git-get-tar-commit-id.txt b/Documentation/git-get-tar-commit-id.txt index 8035736c9..1e2a20dd2 100644 --- a/Documentation/git-get-tar-commit-id.txt +++ b/Documentation/git-get-tar-commit-id.txt @@ -8,6 +8,7 @@ git-get-tar-commit-id - Extract commit ID from an archive created using git-arch SYNOPSIS -------- +[verse] 'git get-tar-commit-id' < <tarfile> diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index e150c77cf..062711139 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -148,6 +148,13 @@ OPTIONS gives the default to color output. Same as `--color=never`. +--break:: + Print an empty line between matches from different files. + +--heading:: + Show the filename above the matches in that file instead of + at the start of each shown line. + -[ABC] <context>:: Show `context` trailing (`A` -- after), or leading (`B` -- before), or both (`C` -- context) lines, and place a @@ -208,15 +215,15 @@ OPTIONS Examples -------- -git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}:: +`git grep {apostrophe}time_t{apostrophe} \-- {apostrophe}*.[ch]{apostrophe}`:: Looks for `time_t` in all tracked .c and .h files in the working directory and its subdirectories. -git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \):: +`git grep -e {apostrophe}#define{apostrophe} --and \( -e MAX_PATH -e PATH_MAX \)`:: Looks for a line that has `#define` and either `MAX_PATH` or `PATH_MAX`. -git grep --all-match -e NODE -e Unexpected:: +`git grep --all-match -e NODE -e Unexpected`:: Looks for a line that has `NODE` or `Unexpected` in files that have lines that match both. diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt index 32a833e0a..004199444 100644 --- a/Documentation/git-gui.txt +++ b/Documentation/git-gui.txt @@ -7,6 +7,7 @@ git-gui - A portable graphical interface to Git SYNOPSIS -------- +[verse] 'git gui' [<command>] [arguments] DESCRIPTION @@ -49,7 +50,7 @@ version:: Examples -------- -git gui blame Makefile:: +`git gui blame Makefile`:: Show the contents of the file 'Makefile' in the current working directory, and provide annotations for both the @@ -58,41 +59,41 @@ git gui blame Makefile:: uncommitted changes (if any) are explicitly attributed to 'Not Yet Committed'. -git gui blame v0.99.8 Makefile:: +`git gui blame v0.99.8 Makefile`:: Show the contents of 'Makefile' in revision 'v0.99.8' and provide annotations for each line. Unlike the above example the file is read from the object database and not the working directory. -git gui blame --line=100 Makefile:: +`git gui blame --line=100 Makefile`:: Loads annotations as described above and automatically scrolls the view to center on line '100'. -git gui citool:: +`git gui citool`:: Make one commit and return to the shell when it is complete. This command returns a non-zero exit code if the window was closed in any way other than by making a commit. -git gui citool --amend:: +`git gui citool --amend`:: Automatically enter the 'Amend Last Commit' mode of the interface. -git gui citool --nocommit:: +`git gui citool --nocommit`:: Behave as normal citool, but instead of making a commit simply terminate with a zero exit code. It still checks that the index does not contain any unmerged entries, so you can use it as a GUI version of linkgit:git-mergetool[1] -git citool:: +`git citool`:: Same as `git gui citool` (above). -git gui browser maint:: +`git gui browser maint`:: Show a browser for the tree of the 'maint' branch. Files selected in the browser can be viewed with the internal diff --git a/Documentation/git-help.txt b/Documentation/git-help.txt index 42aa2b0c0..9e0b3f681 100644 --- a/Documentation/git-help.txt +++ b/Documentation/git-help.txt @@ -7,6 +7,7 @@ git-help - display help information about git SYNOPSIS -------- +[verse] 'git help' [-a|--all|-i|--info|-m|--man|-w|--web] [COMMAND] DESCRIPTION diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt index fefa75219..4d4207386 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -8,6 +8,7 @@ git-http-fetch - Download from a remote git repository via HTTP SYNOPSIS -------- +[verse] 'git http-fetch' [-c] [-t] [-a] [-d] [-v] [-w filename] [--recover] [--stdin] <commit> <url> DESCRIPTION diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index 82ae34b9b..2e67362bd 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -8,6 +8,7 @@ git-http-push - Push objects over HTTP/DAV to another repository SYNOPSIS -------- +[verse] 'git http-push' [--all] [--dry-run] [--force] [--verbose] <url> <ref> [<ref>...] DESCRIPTION diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index 4e09708cc..875d2831a 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -8,6 +8,7 @@ git-imap-send - Send a collection of patches from stdin to an IMAP folder SYNOPSIS -------- +[verse] 'git imap-send' diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 9f97f5a91..a21e34678 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -8,6 +8,7 @@ git-init-db - Creates an empty git repository SYNOPSIS -------- +[verse] 'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index f2777a778..9ac2bbaa5 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -8,6 +8,7 @@ git-init - Create an empty git repository or reinitialize an existing one SYNOPSIS -------- +[verse] 'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--separate-git-dir <git dir>] [--shared[=<permissions>]] [directory] diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 08f85ba04..ea95c9046 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -51,8 +51,8 @@ OPTIONS start:: --start:: - Start the httpd instance and exit. This does not generate - any of the configuration files for spawning a new instance. + Start the httpd instance and exit. Regenerate configuration files + as necessary for spawning a new instance. stop:: --stop:: @@ -62,8 +62,8 @@ stop:: restart:: --restart:: - Restart the httpd instance and exit. This does not generate - any of the configuration files for spawning a new instance. + Restart the httpd instance and exit. Regenerate configuration files + as necessary for spawning a new instance. CONFIGURATION ------------- diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index de5c0d37a..6c934660d 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -8,6 +8,7 @@ git-log - Show commit logs SYNOPSIS -------- +[verse] 'git log' [<options>] [<since>..<until>] [[\--] <path>...] DESCRIPTION @@ -87,45 +88,45 @@ include::diff-generate-patch.txt[] Examples -------- -git log --no-merges:: +`git log --no-merges`:: Show the whole commit history, but skip any merges -git log v2.6.12.. include/scsi drivers/scsi:: +`git log v2.6.12.. include/scsi drivers/scsi`:: Show all commits since version 'v2.6.12' that changed any file in the include/scsi or drivers/scsi subdirectories -git log --since="2 weeks ago" \-- gitk:: +`git log --since="2 weeks ago" \-- gitk`:: Show the changes during the last two weeks to the file 'gitk'. The "--" is necessary to avoid confusion with the *branch* named 'gitk' -git log --name-status release..test:: +`git log --name-status release..test`:: Show the commits that are in the "test" branch but not yet in the "release" branch, along with the list of paths each commit modifies. -git log --follow builtin-rev-list.c:: +`git log --follow builtin-rev-list.c`:: Shows the commits that changed builtin-rev-list.c, including those commits that occurred before the file was given its present name. -git log --branches --not --remotes=origin:: +`git log --branches --not --remotes=origin`:: Shows all commits that are in any of local branches but not in any of remote-tracking branches for 'origin' (what you have that origin doesn't). -git log master --not --remotes=*/master:: +`git log master --not --remotes=*/master`:: Shows all commits that are in local master but not in any remote repository master branches. -git log -p -m --first-parent:: +`git log -p -m --first-parent`:: Shows the history including change diffs, but only from the "main branch" perspective, skipping commits that come from merged diff --git a/Documentation/git-lost-found.txt b/Documentation/git-lost-found.txt index adf7e1c05..c406a1100 100644 --- a/Documentation/git-lost-found.txt +++ b/Documentation/git-lost-found.txt @@ -7,6 +7,7 @@ git-lost-found - Recover lost refs that luckily have not yet been pruned SYNOPSIS -------- +[verse] 'git lost-found' DESCRIPTION diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index ed45662cc..51dc32574 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -8,6 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message SYNOPSIS -------- +[verse] 'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch> diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt index 9b2049d67..4d1b871d9 100644 --- a/Documentation/git-mailsplit.txt +++ b/Documentation/git-mailsplit.txt @@ -7,6 +7,7 @@ git-mailsplit - Simple UNIX mbox splitter program SYNOPSIS -------- +[verse] 'git mailsplit' [-b] [-f<nn>] [-d<prec>] [--keep-cr] -o<directory> [--] [(<mbox>|<Maildir>)...] DESCRIPTION diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt index 635c66956..d7db2a373 100644 --- a/Documentation/git-merge-file.txt +++ b/Documentation/git-merge-file.txt @@ -76,12 +76,12 @@ OPTIONS EXAMPLES -------- -git merge-file README.my README README.upstream:: +`git merge-file README.my README README.upstream`:: combines the changes of README.my and README.upstream since README, tries to merge them and writes the result into README.my. -git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345:: +`git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345`:: merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels `a` and `c` instead of `tmp/a123` and `tmp/c345`. diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 6ce54673b..e0df1b334 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -8,6 +8,7 @@ git-merge-index - Run a merge for files needing merging SYNOPSIS -------- +[verse] 'git merge-index' [-o] [-q] <merge-program> (-a | [--] <file>*) DESCRIPTION diff --git a/Documentation/git-merge-one-file.txt b/Documentation/git-merge-one-file.txt index ee059def7..04e803d5d 100644 --- a/Documentation/git-merge-one-file.txt +++ b/Documentation/git-merge-one-file.txt @@ -8,6 +8,7 @@ git-merge-one-file - The standard helper program to use with git-merge-index SYNOPSIS -------- +[verse] 'git merge-one-file' DESCRIPTION diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt index 3bfa7b422..c5f84b649 100644 --- a/Documentation/git-merge-tree.txt +++ b/Documentation/git-merge-tree.txt @@ -8,6 +8,7 @@ git-merge-tree - Show three-way merge without touching index SYNOPSIS -------- +[verse] 'git merge-tree' <base-tree> <branch1> <branch2> DESCRIPTION diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt index 63ededec1..f98a41b87 100644 --- a/Documentation/git-mergetool--lib.txt +++ b/Documentation/git-mergetool--lib.txt @@ -7,7 +7,8 @@ git-mergetool--lib - Common git merge tool shell scriptlets SYNOPSIS -------- -'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool--lib"' +[verse] +'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool{litdd}lib"' DESCRIPTION ----------- diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 8c79ae8d2..347091010 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -7,6 +7,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts SYNOPSIS -------- +[verse] 'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>...] DESCRIPTION diff --git a/Documentation/git-mktag.txt b/Documentation/git-mktag.txt index 037ab1045..65e167a5c 100644 --- a/Documentation/git-mktag.txt +++ b/Documentation/git-mktag.txt @@ -8,6 +8,7 @@ git-mktag - Creates a tag object SYNOPSIS -------- +[verse] 'git mktag' < signature_file DESCRIPTION diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt index afe21be64..5c6ebdfad 100644 --- a/Documentation/git-mktree.txt +++ b/Documentation/git-mktree.txt @@ -8,6 +8,7 @@ git-mktree - Build a tree-object from ls-tree formatted text SYNOPSIS -------- +[verse] 'git mktree' [-z] [--missing] [--batch] DESCRIPTION diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt index db0e030d6..b8db37396 100644 --- a/Documentation/git-mv.txt +++ b/Documentation/git-mv.txt @@ -8,6 +8,7 @@ git-mv - Move or rename a file, a directory, or a symlink SYNOPSIS -------- +[verse] 'git mv' <options>... <args>... DESCRIPTION diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt index db9f0f705..f2869da57 100644 --- a/Documentation/git-pack-redundant.txt +++ b/Documentation/git-pack-redundant.txt @@ -8,6 +8,7 @@ git-pack-redundant - Find redundant pack files SYNOPSIS -------- +[verse] 'git pack-redundant' [ --verbose ] [ --alt-odb ] < --all | .pack filename ... > DESCRIPTION diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt index 54b92534c..a3c6677bf 100644 --- a/Documentation/git-pack-refs.txt +++ b/Documentation/git-pack-refs.txt @@ -7,6 +7,7 @@ git-pack-refs - Pack heads and tags for efficient repository access SYNOPSIS -------- +[verse] 'git pack-refs' [--all] [--no-prune] DESCRIPTION diff --git a/Documentation/git-parse-remote.txt b/Documentation/git-parse-remote.txt index 02217f6ba..a45ea1ece 100644 --- a/Documentation/git-parse-remote.txt +++ b/Documentation/git-parse-remote.txt @@ -8,6 +8,7 @@ git-parse-remote - Routines to help parsing remote repository access parameters SYNOPSIS -------- +[verse] '. "$(git --exec-path)/git-parse-remote"' DESCRIPTION diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt index 50e26f43c..90268f02e 100644 --- a/Documentation/git-patch-id.txt +++ b/Documentation/git-patch-id.txt @@ -7,6 +7,7 @@ git-patch-id - Compute unique ID for a patch SYNOPSIS -------- +[verse] 'git patch-id' < <patch> DESCRIPTION diff --git a/Documentation/git-peek-remote.txt b/Documentation/git-peek-remote.txt index a34d62f0d..87ea3fb05 100644 --- a/Documentation/git-peek-remote.txt +++ b/Documentation/git-peek-remote.txt @@ -8,6 +8,7 @@ git-peek-remote - List the references in a remote repository SYNOPSIS -------- +[verse] 'git peek-remote' [--upload-pack=<git-upload-pack>] [<host>:]<directory> DESCRIPTION diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt index 9e6202cdf..80dc022ed 100644 --- a/Documentation/git-prune-packed.txt +++ b/Documentation/git-prune-packed.txt @@ -8,6 +8,7 @@ git-prune-packed - Remove extra objects that are already in pack files SYNOPSIS -------- +[verse] 'git prune-packed' [-n|--dry-run] [-q|--quiet] diff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt index f616a739e..80d01b057 100644 --- a/Documentation/git-prune.txt +++ b/Documentation/git-prune.txt @@ -8,6 +8,7 @@ git-prune - Prune all unreachable objects from the object database SYNOPSIS -------- +[verse] 'git prune' [-n] [-v] [--expire <expire>] [--] [<head>...] DESCRIPTION diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 14609cbd4..e1da46876 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -8,6 +8,7 @@ git-pull - Fetch from and merge with another repository or a local branch SYNOPSIS -------- +[verse] 'git pull' [options] [<repository> [<refspec>...]] diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 88acfcd4c..49c6e9fa5 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -327,12 +327,12 @@ a case where you do mean to lose history. Examples -------- -git push:: +`git push`:: Works like `git push <remote>`, where <remote> is the current branch's remote (or `origin`, if no remote is configured for the current branch). -git push origin:: +`git push origin`:: Without additional configuration, works like `git push origin :`. + @@ -344,45 +344,45 @@ use `git config remote.origin.push HEAD`. Any valid <refspec> (like the ones in the examples below) can be configured as the default for `git push origin`. -git push origin ::: +`git push origin :`:: Push "matching" branches to `origin`. See <refspec> in the <<OPTIONS,OPTIONS>> section above for a description of "matching" branches. -git push origin master:: +`git push origin master`:: Find a ref that matches `master` in the source repository (most likely, it would find `refs/heads/master`), and update the same ref (e.g. `refs/heads/master`) in `origin` repository with it. If `master` did not exist remotely, it would be created. -git push origin HEAD:: +`git push origin HEAD`:: A handy way to push the current branch to the same name on the remote. -git push origin master:satellite/master dev:satellite/dev:: +`git push origin master:satellite/master dev:satellite/dev`:: Use the source ref that matches `master` (e.g. `refs/heads/master`) to update the ref that matches `satellite/master` (most probably `refs/remotes/satellite/master`) in the `origin` repository, then do the same for `dev` and `satellite/dev`. -git push origin HEAD:master:: +`git push origin HEAD:master`:: Push the current branch to the remote ref matching `master` in the `origin` repository. This form is convenient to push the current branch without thinking about its local name. -git push origin master:refs/heads/experimental:: +`git push origin master:refs/heads/experimental`:: Create the branch `experimental` in the `origin` repository by copying the current `master` branch. This form is only needed to create a new branch or tag in the remote repository when the local name and the remote name are different; otherwise, the ref name on its own will work. -git push origin :experimental:: +`git push origin :experimental`:: Find a ref that matches `experimental` in the `origin` repository (e.g. `refs/heads/experimental`), and delete it. -git push origin {plus}dev:master:: +`git push origin {plus}dev:master`:: Update the origin repository's master branch with the dev branch, allowing non-fast-forward updates. *This can leave unreferenced commits dangling in the origin repository.* Consider the diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 46a96f231..c45d53c6e 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,6 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- +[verse] 'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] [--no-sparse-checkout] diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 9a075bc4d..504945c69 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -12,7 +12,6 @@ SYNOPSIS [<upstream>] [<branch>] 'git rebase' [-i | --interactive] [options] --onto <newbase> --root [<branch>] - 'git rebase' --continue | --skip | --abort DESCRIPTION @@ -46,7 +45,7 @@ with a different commit message or timestamp will be skipped). It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure and run `git rebase --continue`. Another option is to bypass the commit -that caused the merge failure with `git rebase --skip`. To restore the +that caused the merge failure with `git rebase --skip`. To check out the original <branch> and remove the .git/rebase-apply working files, use the command `git rebase --abort` instead. @@ -233,7 +232,11 @@ leave out at most one of A and B, in which case it defaults to HEAD. Restart the rebasing process after having resolved a merge conflict. --abort:: - Restore the original branch and abort the rebase operation. + Abort the rebase operation and reset HEAD to the original + branch. If <branch> was provided when the rebase operation was + started, then HEAD will be reset to <branch>. Otherwise HEAD + will be reset to where it was when the rebase operation was + started. --skip:: Restart the rebasing process by skipping the current patch. diff --git a/Documentation/git-receive-pack.txt b/Documentation/git-receive-pack.txt index f34e0ae1b..459c08598 100644 --- a/Documentation/git-receive-pack.txt +++ b/Documentation/git-receive-pack.txt @@ -8,6 +8,7 @@ git-receive-pack - Receive what is pushed into the repository SYNOPSIS -------- +[verse] 'git-receive-pack' <directory> DESCRIPTION diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt index 09057bf90..976dc1493 100644 --- a/Documentation/git-reflog.txt +++ b/Documentation/git-reflog.txt @@ -8,6 +8,7 @@ git-reflog - Manage reflog information SYNOPSIS -------- +[verse] 'git reflog' <subcommand> <options> DESCRIPTION diff --git a/Documentation/git-relink.txt b/Documentation/git-relink.txt index 989337648..3b33c9951 100644 --- a/Documentation/git-relink.txt +++ b/Documentation/git-relink.txt @@ -7,6 +7,7 @@ git-relink - Hardlink common objects in local repositories SYNOPSIS -------- +[verse] 'git relink' [--safe] <dir>... <master_dir> DESCRIPTION diff --git a/Documentation/git-remote-ext.txt b/Documentation/git-remote-ext.txt index 68263a6a5..8a8e1d775 100644 --- a/Documentation/git-remote-ext.txt +++ b/Documentation/git-remote-ext.txt @@ -7,6 +7,7 @@ git-remote-ext - Bridge smart transport to external command. SYNOPSIS -------- +[verse] git remote add <nick> "ext::<command>[ <arguments>...]" DESCRIPTION diff --git a/Documentation/git-remote-fd.txt b/Documentation/git-remote-fd.txt index 4aecd4d18..f095d57d0 100644 --- a/Documentation/git-remote-fd.txt +++ b/Documentation/git-remote-fd.txt @@ -35,19 +35,19 @@ GIT_TRANSLOOP_DEBUG:: EXAMPLES -------- -git fetch fd::17 master:: +`git fetch fd::17 master`:: Fetch master, using file descriptor #17 to communicate with git-upload-pack. -git fetch fd::17/foo master:: +`git fetch fd::17/foo master`:: Same as above. -git push fd::7,8 master (as URL):: +`git push fd::7,8 master (as URL)`:: Push master, using file descriptor #7 to read data from git-receive-pack and file descriptor #8 to write data to same service. -git push fd::7,8/bar master:: +`git push fd::7,8/bar master`:: Same as above. Documentation diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt index 58f6ad499..4f83dea5a 100644 --- a/Documentation/git-remote-helpers.txt +++ b/Documentation/git-remote-helpers.txt @@ -7,6 +7,7 @@ git-remote-helpers - Helper programs to interact with remote repositories SYNOPSIS -------- +[verse] 'git remote-<transport>' <repository> [<URL>] DESCRIPTION @@ -47,6 +48,9 @@ arguments. The first argument specifies a remote repository as in git; it is either the name of a configured remote or a URL. The second argument specifies a URL; it is usually of the form '<transport>://<address>', but any arbitrary string is possible. +The 'GIT_DIR' environment variable is set up for the remote helper +and can be used to determine where to store additional data or from +which directory to invoke auxiliary git commands. When git encounters a URL of the form '<transport>://<address>', where '<transport>' is a protocol that it cannot handle natively, it diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index 528f34a13..5a8c5061f 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -60,11 +60,11 @@ the remote repository. + With `-t <branch>` option, instead of the default glob refspec for the remote to track all branches under -`$GIT_DIR/remotes/<name>/`, a refspec to track only `<branch>` +the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>` is created. You can give more than one `-t <branch>` to track multiple branches without grabbing all branches. + -With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set +With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set up to point at remote's `<master>` branch. See also the set-head command. + When a fetch mirror is created with `\--mirror=fetch`, the refs will not @@ -92,24 +92,25 @@ configuration settings for the remote are removed. 'set-head':: -Sets or deletes the default branch (`$GIT_DIR/remotes/<name>/HEAD`) for +Sets or deletes the default branch (i.e. the target of the +symbolic-ref `refs/remotes/<name>/HEAD`) for the named remote. Having a default branch for a remote is not required, but allows the name of the remote to be specified in lieu of a specific branch. For example, if the default branch for `origin` is set to `master`, then `origin` may be specified wherever you would normally specify `origin/master`. + -With `-d`, `$GIT_DIR/remotes/<name>/HEAD` is deleted. +With `-d`, the symbolic ref `refs/remotes/<name>/HEAD` is deleted. + -With `-a`, the remote is queried to determine its `HEAD`, then -`$GIT_DIR/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote +With `-a`, the remote is queried to determine its `HEAD`, then the +symbolic-ref `refs/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote `HEAD` is pointed at `next`, "`git remote set-head origin -a`" will set -`$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will +the symbolic-ref `refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will only work if `refs/remotes/origin/next` already exists; if not it must be fetched first. + -Use `<branch>` to set `$GIT_DIR/remotes/<name>/HEAD` explicitly. e.g., "git -remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to +Use `<branch>` to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., "git +remote set-head origin master" will set the symbolic-ref `refs/remotes/origin/HEAD` to `refs/remotes/origin/master`. This will only work if `refs/remotes/origin/master` already exists; if not it must be fetched first. + diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 0decee240..40af32115 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -8,6 +8,7 @@ git-repack - Pack unpacked objects in a repository SYNOPSIS -------- +[verse] 'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [--window=<n>] [--depth=<n>] DESCRIPTION diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index a0d1fa659..9ec115b9e 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -8,6 +8,7 @@ git-repo-config - Get and set repository or global options SYNOPSIS -------- +[verse] 'git repo-config' ... diff --git a/Documentation/git-request-pull.txt b/Documentation/git-request-pull.txt index 3521d8e3c..b99681ce8 100644 --- a/Documentation/git-request-pull.txt +++ b/Documentation/git-request-pull.txt @@ -7,6 +7,7 @@ git-request-pull - Generates a summary of pending changes SYNOPSIS -------- +[verse] 'git request-pull' [-p] <start> <url> [<end>] DESCRIPTION diff --git a/Documentation/git-rerere.txt b/Documentation/git-rerere.txt index 52db1d80c..a6253ba61 100644 --- a/Documentation/git-rerere.txt +++ b/Documentation/git-rerere.txt @@ -7,6 +7,7 @@ git-rerere - Reuse recorded resolution of conflicted merges SYNOPSIS -------- +[verse] 'git rerere' ['clear'|'forget' <pathspec>|'diff'|'status'|'gc'] DESCRIPTION diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 02c44c999..42c9676ea 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -8,6 +8,7 @@ git-rev-parse - Pick out and massage parameters SYNOPSIS -------- +[verse] 'git rev-parse' [ --option ] <args>... DESCRIPTION diff --git a/Documentation/git-revert.txt b/Documentation/git-revert.txt index ac10cfbb1..b311d59c7 100644 --- a/Documentation/git-revert.txt +++ b/Documentation/git-revert.txt @@ -7,6 +7,7 @@ git-revert - Revert some existing commits SYNOPSIS -------- +[verse] 'git revert' [--edit | --no-edit] [-n] [-m parent-number] [-s] <commit>... DESCRIPTION @@ -23,7 +24,7 @@ throw away all uncommitted changes in your working directory, you should see linkgit:git-reset[1], particularly the '--hard' option. If you want to extract specific files as they were in another commit, you should see linkgit:git-checkout[1], specifically the `git checkout -<commit> -- <filename>` syntax. Take care with these alternatives as +<commit> \-- <filename>` syntax. Take care with these alternatives as both will discard uncommitted changes in your working directory. OPTIONS @@ -92,12 +93,12 @@ effect to your index in a row. EXAMPLES -------- -git revert HEAD~3:: +`git revert HEAD~3`:: Revert the changes specified by the fourth last commit in HEAD and create a new commit with the reverted changes. -git revert -n master{tilde}5..master{tilde}2:: +`git revert -n master{tilde}5..master{tilde}2`:: Revert the changes done by commits from the fifth last commit in master (included) to the third last commit in master diff --git a/Documentation/git-rm.txt b/Documentation/git-rm.txt index 8c0554f97..665ad4dda 100644 --- a/Documentation/git-rm.txt +++ b/Documentation/git-rm.txt @@ -7,6 +7,7 @@ git-rm - Remove files from the working tree and from the index SYNOPSIS -------- +[verse] 'git rm' [-f | --force] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>... DESCRIPTION @@ -136,7 +137,7 @@ git diff --name-only --diff-filter=D -z | xargs -0 git rm --cached EXAMPLES -------- -git rm Documentation/\*.txt:: +`git rm Documentation/\*.txt`:: Removes all `*.txt` files from the index that are under the `Documentation` directory and any of its subdirectories. + @@ -144,7 +145,7 @@ Note that the asterisk `*` is quoted from the shell in this example; this lets git, and not the shell, expand the pathnames of files and subdirectories under the `Documentation/` directory. -git rm -f git-*.sh:: +`git rm -f git-*.sh`:: Because this example lets the shell expand the asterisk (i.e. you are listing the files explicitly), it does not remove `subdir/git-foo.sh`. diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index 5a168cfab..327233c85 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -8,6 +8,7 @@ git-send-email - Send a collection of patches as emails SYNOPSIS -------- +[verse] 'git send-email' [options] <file|directory|rev-list options>... diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 17f8f5552..bd3eaa69b 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -8,6 +8,7 @@ git-send-pack - Push objects over git protocol to another repository SYNOPSIS -------- +[verse] 'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...] DESCRIPTION diff --git a/Documentation/git-sh-i18n--envsubst.txt b/Documentation/git-sh-i18n--envsubst.txt index 61e4c08da..5c3ec327b 100644 --- a/Documentation/git-sh-i18n--envsubst.txt +++ b/Documentation/git-sh-i18n--envsubst.txt @@ -1,5 +1,5 @@ -git-sh-i18n--envsubst(1) -======================== +git-sh-i18n{litdd}envsubst(1) +============================= NAME ---- @@ -10,8 +10,8 @@ SYNOPSIS [verse] eval_gettext () { printf "%s" "$1" | ( - export PATH $('git sh-i18n--envsubst' --variables "$1"); - 'git sh-i18n--envsubst' "$1" + export PATH $('git sh-i18n{litdd}envsubst' --variables "$1"); + 'git sh-i18n{litdd}envsubst' "$1" ) } @@ -22,7 +22,7 @@ This is not a command the end user would want to run. Ever. This documentation is meant for people who are studying the plumbing scripts and/or are writing new ones. -git-sh-i18n--envsubst is Git's stripped-down copy of the GNU +'git sh-i18n{litdd}envsubst' is Git's stripped-down copy of the GNU `envsubst(1)` program that comes with the GNU gettext package. It's used internally by linkgit:git-sh-i18n[1] to interpolate the variables passed to the the `eval_gettext` function. diff --git a/Documentation/git-sh-i18n.txt b/Documentation/git-sh-i18n.txt index 3b1f7ac7b..60cf49cb2 100644 --- a/Documentation/git-sh-i18n.txt +++ b/Documentation/git-sh-i18n.txt @@ -7,6 +7,7 @@ git-sh-i18n - Git's i18n setup code for shell scripts SYNOPSIS -------- +[verse] '. "$(git --exec-path)/git-sh-i18n"' DESCRIPTION @@ -34,7 +35,7 @@ gettext:: eval_gettext:: Currently a dummy fall-through function implemented as a wrapper around `printf(1)` with variables expanded by the - linkgit:git-sh-i18n--envsubst[1] helper. Will be replaced by a + linkgit:git-sh-i18n{litdd}envsubst[1] helper. Will be replaced by a real gettext implementation in a later version. GIT diff --git a/Documentation/git-sh-setup.txt b/Documentation/git-sh-setup.txt index 27fd8ba85..a2f346ca7 100644 --- a/Documentation/git-sh-setup.txt +++ b/Documentation/git-sh-setup.txt @@ -7,6 +7,7 @@ git-sh-setup - Common git shell script setup code SYNOPSIS -------- +[verse] '. "$(git --exec-path)/git-sh-setup"' DESCRIPTION diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index d7d4b9289..9b9250600 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -8,6 +8,7 @@ git-shell - Restricted login shell for Git-only SSH access SYNOPSIS -------- +[verse] 'git shell' [-c <command> <argument>] DESCRIPTION diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index ee4559b6f..a8e77b535 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -13,7 +13,6 @@ SYNOPSIS [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [(<rev> | <glob>)...] - 'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>] DESCRIPTION diff --git a/Documentation/git-show-index.txt b/Documentation/git-show-index.txt index c4d99f102..2dcbbb245 100644 --- a/Documentation/git-show-index.txt +++ b/Documentation/git-show-index.txt @@ -8,6 +8,7 @@ git-show-index - Show packed archive index SYNOPSIS -------- +[verse] 'git show-index' < idx-file diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index 7f075e84f..1e38819e6 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -8,6 +8,7 @@ git-show - Show various types of objects SYNOPSIS -------- +[verse] 'git show' [options] <object>... DESCRIPTION @@ -47,23 +48,23 @@ include::pretty-formats.txt[] EXAMPLES -------- -git show v1.0.0:: +`git show v1.0.0`:: Shows the tag `v1.0.0`, along with the object the tags points at. -git show v1.0.0^\{tree\}:: +`git show v1.0.0^\{tree\}`:: Shows the tree pointed to by the tag `v1.0.0`. -git show -s --format=%s v1.0.0^\{commit\}:: +`git show -s --format=%s v1.0.0^\{commit\}`:: Shows the subject of the commit pointed to by the tag `v1.0.0`. -git show next~10:Documentation/README:: +`git show next~10:Documentation/README`:: Shows the contents of the file `Documentation/README` as they were current in the 10th last commit of the branch `next`. -git show master:Makefile master:t/Makefile:: +`git show master:Makefile master:t/Makefile`:: Concatenates the contents of said Makefiles in the head of the branch `master`. diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 15f051fa4..43af38aa4 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,8 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] +'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [<message>]] 'git stash' clear 'git stash' create @@ -42,7 +43,7 @@ is also possible). OPTIONS ------- -save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: +save [-p|--patch] [--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q|--quiet] [<message>]:: Save your local modifications to a new 'stash', and run `git reset --hard` to revert them. The <message> part is optional and gives @@ -54,6 +55,11 @@ save [-p|--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: If the `--keep-index` option is used, all changes already added to the index are left intact. + +If the `--include-untracked` option is used, all untracked files are also +stashed and then cleaned up with `git clean`, leaving the working directory +in a very clean state. If the `--all` option is used instead then the +ignored files are stashed and cleaned in addition to the untracked files. ++ With `--patch`, you can interactively select hunks from 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 diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index 38cb741f1..3d51717bb 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -8,6 +8,7 @@ git-status - Show the working tree status SYNOPSIS -------- +[verse] 'git status' [<options>...] [--] [<pathspec>...] DESCRIPTION @@ -69,6 +70,9 @@ configuration variable documented in linkgit:git-config[1]. (and suppresses the output of submodule summaries when the config option `status.submodulesummary` is set). +--ignored:: + Show ignored files as well. + -z:: Terminate entries with NUL, instead of LF. This implies the `--porcelain` output format if no other format is given. @@ -119,7 +123,8 @@ codes can be interpreted as follows: * 'C' = copied * 'U' = updated but unmerged -Ignored files are not listed. +Ignored files are not listed, unless `--ignored` option is in effect, +in which case `XY` are `!!`. X Y Meaning ------------------------------------------------- @@ -142,6 +147,7 @@ Ignored files are not listed. U U unmerged, both modified ------------------------------------------------- ? ? untracked + ! ! ignored ------------------------------------------------- If -b is used the short-format status is preceded by a line diff --git a/Documentation/git-stripspace.txt b/Documentation/git-stripspace.txt index 10509cc45..b78f031cd 100644 --- a/Documentation/git-stripspace.txt +++ b/Documentation/git-stripspace.txt @@ -8,6 +8,7 @@ git-stripspace - Filter out empty lines SYNOPSIS -------- +[verse] 'git stripspace' [-s | --strip-comments] < <stream> DESCRIPTION diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 3b05498c1..67cf5f0f8 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -79,7 +79,9 @@ to exist in the superproject. If <path> is not given, the <repository> is the URL of the new submodule's origin repository. This may be either an absolute URL, or (if it begins with ./ or ../), the location relative to the superproject's origin -repository. +repository. If the superproject doesn't have an origin configured +the superproject is its own authoritative upstream and the current +working directory is used instead. + <path> is the relative location for the cloned submodule to exist in the superproject. If <path> does not exist, then the @@ -176,12 +178,14 @@ commit for each submodule. sync:: Synchronizes submodules' remote URL configuration setting - to the value specified in .gitmodules. This is useful when + to the value specified in .gitmodules. It will only affect those + submodules which already have an url entry in .git/config (that is the + case when they are initialized or freshly added). This is useful when submodule URLs change upstream and you need to update your local repositories accordingly. + "git submodule sync" synchronizes all submodules while -"git submodule sync -- A" synchronizes submodule "A" only. +"git submodule sync \-- A" synchronizes submodule "A" only. OPTIONS ------- diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 713e52303..ed5eca1fc 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -7,6 +7,7 @@ git-svn - Bidirectional operation between a Subversion repository and git SYNOPSIS -------- +[verse] 'git svn' <command> [options] [arguments] DESCRIPTION diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index d7795ed65..75b1ae506 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -7,6 +7,7 @@ git-symbolic-ref - Read and modify symbolic refs SYNOPSIS -------- +[verse] 'git symbolic-ref' [-q] [-m <reason>] <name> [<ref>] DESCRIPTION diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index d82f62120..fb1c0ac69 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <tagname> [<commit> | <object>] 'git tag' -d <tagname>... -'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>] +'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>...] 'git tag' -v <tagname>... DESCRIPTION @@ -69,8 +69,11 @@ OPTIONS If the tag is not annotated, the commit message is displayed instead. -l <pattern>:: - List tags with names that match the given pattern (or all if no pattern is given). - Typing "git tag" without arguments, also lists all tags. + List tags with names that match the given pattern (or all if no + pattern is given). Running "git tag" without arguments also + lists all tags. The pattern is a shell wildcard (i.e., matched + using fnmatch(3)). Multiple patterns may be given; if any of + them matches, the tag is shown. --contains <commit>:: Only list tags which contain the specified commit. diff --git a/Documentation/git-tar-tree.txt b/Documentation/git-tar-tree.txt index 5f1575425..346e7a207 100644 --- a/Documentation/git-tar-tree.txt +++ b/Documentation/git-tar-tree.txt @@ -8,6 +8,7 @@ git-tar-tree - Create a tar archive of the files in the named tree object SYNOPSIS -------- +[verse] 'git tar-tree' [--remote=<repo>] <tree-ish> [ <base> ] DESCRIPTION @@ -52,26 +53,26 @@ tar.umask:: EXAMPLES -------- -git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -):: +`git tar-tree HEAD junk | (cd /var/tmp/ && tar xf -)`:: Create a tar archive that contains the contents of the latest commit on the current branch, and extracts it in `/var/tmp/junk` directory. -git tar-tree v1.4.0 git-1.4.0 | gzip >git-1.4.0.tar.gz:: +`git tar-tree v1.4.0 git-1.4.0 | gzip >git-1.4.0.tar.gz`:: Create a tarball for v1.4.0 release. -git tar-tree v1.4.0{caret}\{tree\} git-1.4.0 | gzip >git-1.4.0.tar.gz:: +`git tar-tree v1.4.0{caret}\{tree\} git-1.4.0 | gzip >git-1.4.0.tar.gz`:: Create a tarball for v1.4.0 release, but without a global extended pax header. -git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar:: +`git tar-tree --remote=example.com:git.git v1.4.0 >git-1.4.0.tar`:: Get a tarball v1.4.0 from example.com. -git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar:: +`git tar-tree HEAD:Documentation/ git-docs > git-1.4.0-docs.tar`:: Put everything in the current head's Documentation/ directory into 'git-1.4.0-docs.tar', with the prefix 'git-docs/'. diff --git a/Documentation/git-unpack-file.txt b/Documentation/git-unpack-file.txt index c49d727f7..e9f148a00 100644 --- a/Documentation/git-unpack-file.txt +++ b/Documentation/git-unpack-file.txt @@ -9,6 +9,7 @@ git-unpack-file - Creates a temporary file with a blob's contents SYNOPSIS -------- +[verse] 'git unpack-file' <blob> DESCRIPTION diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index dd7799095..ff23494e7 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -8,6 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive SYNOPSIS -------- +[verse] 'git unpack-objects' [-n] [-q] [-r] [--strict] <pack-file diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index e25a65a80..d377a3524 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,6 +7,7 @@ git-update-ref - Update the object name stored in a ref safely SYNOPSIS -------- +[verse] 'git update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>]) DESCRIPTION @@ -60,8 +61,9 @@ still contains <oldvalue>. Logging Updates --------------- -If config parameter "core.logAllRefUpdates" is true or the file -"$GIT_DIR/logs/<ref>" exists then `git update-ref` will append +If config parameter "core.logAllRefUpdates" is true and the ref is one under +"refs/heads/", "refs/remotes/", "refs/notes/", or the symbolic ref HEAD; or +the file "$GIT_DIR/logs/<ref>" exists then `git update-ref` will append a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all symbolic refs before creating the log name) describing the change in ref value. Log lines are formatted as: diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt index 775024da3..bd0e36492 100644 --- a/Documentation/git-update-server-info.txt +++ b/Documentation/git-update-server-info.txt @@ -8,6 +8,7 @@ git-update-server-info - Update auxiliary info file to help dumb servers SYNOPSIS -------- +[verse] 'git update-server-info' [--force] DESCRIPTION diff --git a/Documentation/git-upload-archive.txt b/Documentation/git-upload-archive.txt index acbf634f8..4d52d3833 100644 --- a/Documentation/git-upload-archive.txt +++ b/Documentation/git-upload-archive.txt @@ -8,6 +8,7 @@ git-upload-archive - Send archive back to git-archive SYNOPSIS -------- +[verse] 'git upload-archive' <directory> DESCRIPTION diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index 4c0ca9ded..a58e90ca8 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -8,6 +8,7 @@ git-upload-pack - Send objects packed back to git-fetch-pack SYNOPSIS -------- +[verse] 'git-upload-pack' [--strict] [--timeout=<n>] <directory> DESCRIPTION diff --git a/Documentation/git-var.txt b/Documentation/git-var.txt index 6498f7cb6..5317cc247 100644 --- a/Documentation/git-var.txt +++ b/Documentation/git-var.txt @@ -8,6 +8,7 @@ git-var - Show a git logical variable SYNOPSIS -------- +[verse] 'git var' ( -l | <variable> ) DESCRIPTION diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt index 7c2428d56..cd230769f 100644 --- a/Documentation/git-verify-pack.txt +++ b/Documentation/git-verify-pack.txt @@ -8,6 +8,7 @@ git-verify-pack - Validate packed git archive files SYNOPSIS -------- +[verse] 'git verify-pack' [-v|--verbose] [-s|--stat-only] [--] <pack>.idx ... diff --git a/Documentation/git-verify-tag.txt b/Documentation/git-verify-tag.txt index 8c9a71865..5ff76e892 100644 --- a/Documentation/git-verify-tag.txt +++ b/Documentation/git-verify-tag.txt @@ -7,6 +7,7 @@ git-verify-tag - Check the GPG signature of tags SYNOPSIS -------- +[verse] 'git verify-tag' <tag>... DESCRIPTION diff --git a/Documentation/git-web--browse.txt b/Documentation/git-web--browse.txt index 69d92fa00..c2bc87bc6 100644 --- a/Documentation/git-web--browse.txt +++ b/Documentation/git-web--browse.txt @@ -7,6 +7,7 @@ git-web--browse - git helper script to launch a web browser SYNOPSIS -------- +[verse] 'git web{litdd}browse' [OPTIONS] URL/FILE ... DESCRIPTION @@ -68,7 +69,7 @@ browser.<tool>.path You can explicitly provide a full path to your preferred browser by setting the configuration variable 'browser.<tool>.path'. For example, you can configure the absolute path to firefox by setting -'browser.firefox.path'. Otherwise, 'git web--browse' assumes the tool +'browser.firefox.path'. Otherwise, 'git web{litdd}browse' assumes the tool is available in PATH. browser.<tool>.cmd diff --git a/Documentation/git-whatchanged.txt b/Documentation/git-whatchanged.txt index 31f3663ae..76c7f7eec 100644 --- a/Documentation/git-whatchanged.txt +++ b/Documentation/git-whatchanged.txt @@ -8,6 +8,7 @@ git-whatchanged - Show logs with difference each commit introduces SYNOPSIS -------- +[verse] 'git whatchanged' <option>... DESCRIPTION @@ -52,12 +53,12 @@ include::pretty-formats.txt[] Examples -------- -git whatchanged -p v2.6.12.. include/scsi drivers/scsi:: +`git whatchanged -p v2.6.12.. include/scsi drivers/scsi`:: Show as patches the commits since version 'v2.6.12' that changed any file in the include/scsi or drivers/scsi subdirectories -git whatchanged --since="2 weeks ago" \-- gitk:: +`git whatchanged --since="2 weeks ago" \-- gitk`:: Show the changes during the last two weeks to the file 'gitk'. The "--" is necessary to avoid confusion with the *branch* named diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt index e8c94c135..f22041a9d 100644 --- a/Documentation/git-write-tree.txt +++ b/Documentation/git-write-tree.txt @@ -8,6 +8,7 @@ git-write-tree - Create a tree object from the current index SYNOPSIS -------- +[verse] 'git write-tree' [--missing-ok] [--prefix=<prefix>/] DESCRIPTION diff --git a/Documentation/git.txt b/Documentation/git.txt index 3c7a83234..0172cd701 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -523,16 +523,15 @@ Any git command accepting any <object> can also use the following symbolic notation: HEAD:: - indicates the head of the current branch (i.e. the - contents of `$GIT_DIR/HEAD`). + indicates the head of the current branch. <tag>:: a valid tag 'name' - (i.e. the contents of `$GIT_DIR/refs/tags/<tag>`). + (i.e. a `refs/tags/<tag>` reference). <head>:: a valid head 'name' - (i.e. the contents of `$GIT_DIR/refs/heads/<head>`). + (i.e. a `refs/heads/<head>` reference). For a more complete list of ways to spell object names, see "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7]. diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 412c55b54..2bbe76b5d 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -79,7 +79,7 @@ Attributes for all users on a system should be placed in the `$(prefix)/etc/gitattributes` file. Sometimes you would need to override an setting of an attribute -for a path to `unspecified` state. This can be done by listing +for a path to `Unspecified` state. This can be done by listing the name of the attribute prefixed with an exclamation point `!`. @@ -868,7 +868,7 @@ If this attribute is not set or has an invalid value, the value of the (See linkgit:git-config[1]). -USING ATTRIBUTE MACROS +USING MACRO ATTRIBUTES ---------------------- You do not want any end-of-line conversions applied to, nor textual diffs @@ -879,24 +879,27 @@ produced for, any binary file you track. You would need to specify e.g. ------------ but that may become cumbersome, when you have many attributes. Using -attribute macros, you can specify groups of attributes set or unset at -the same time. The system knows a built-in attribute macro, `binary`: +macro attributes, you can define an attribute that, when set, also +sets or unsets a number of other attributes at the same time. The +system knows a built-in macro attribute, `binary`: ------------ *.jpg binary ------------ -which is equivalent to the above. Note that the attribute macros can only -be "Set" (see the above example that sets "binary" macro as if it were an -ordinary attribute --- setting it in turn unsets "text" and "diff"). +Setting the "binary" attribute also unsets the "text" and "diff" +attributes as above. Note that macro attributes can only be "Set", +though setting one might have the effect of setting or unsetting other +attributes or even returning other attributes to the "Unspecified" +state. -DEFINING ATTRIBUTE MACROS +DEFINING MACRO ATTRIBUTES ------------------------- -Custom attribute macros can be defined only in the `.gitattributes` file -at the toplevel (i.e. not in any subdirectory). The built-in attribute -macro "binary" is equivalent to: +Custom macro attributes can be defined only in the `.gitattributes` +file at the toplevel (i.e. not in any subdirectory). The built-in +macro attribute "binary" is equivalent to: ------------ [attr]binary -diff -text diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt index d861ec452..aeb0cdc97 100644 --- a/Documentation/gitcvs-migration.txt +++ b/Documentation/gitcvs-migration.txt @@ -7,7 +7,8 @@ gitcvs-migration - git for CVS users SYNOPSIS -------- -git cvsimport * +[verse] +'git cvsimport' * DESCRIPTION ----------- diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 6af29a460..370624c17 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -7,6 +7,7 @@ gitdiffcore - Tweaking diff output SYNOPSIS -------- +[verse] 'git diff' * DESCRIPTION diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index e10ac58ca..a17a35493 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -7,6 +7,7 @@ gitk - The git repository browser SYNOPSIS -------- +[verse] 'gitk' [<option>...] [<revs>] [--] [<path>...] DESCRIPTION diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index 7fe5848d1..f1e4422ac 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -7,6 +7,7 @@ gittutorial-2 - A tutorial introduction to git: part two SYNOPSIS -------- +[verse] git * DESCRIPTION diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 0982f74ef..dee050567 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -7,6 +7,7 @@ gittutorial - A tutorial introduction to git (for version 1.5.1 or newer) SYNOPSIS -------- +[verse] git * DESCRIPTION diff --git a/Documentation/gitworkflows.txt b/Documentation/gitworkflows.txt index 1ef55fffc..5e4f362ff 100644 --- a/Documentation/gitworkflows.txt +++ b/Documentation/gitworkflows.txt @@ -7,6 +7,7 @@ gitworkflows - An overview of recommended workflows with git SYNOPSIS -------- +[verse] git * diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 8f62d1abe..3595b586b 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -161,8 +161,8 @@ to point at the new commit. [[def_head]]head:: A <<def_ref,named reference>> to the <<def_commit,commit>> at the tip of a - <<def_branch,branch>>. Heads are stored in - `$GIT_DIR/refs/heads/`, except when using packed refs. (See + <<def_branch,branch>>. Heads are stored in a file in + `$GIT_DIR/refs/heads/` directory, except when using packed refs. (See linkgit:git-pack-refs[1].) [[def_HEAD]]HEAD:: @@ -170,8 +170,8 @@ to point at the new commit. working tree>> is normally derived from the state of the tree referred to by HEAD. HEAD is a reference to one of the <<def_head,heads>> in your repository, except when using a - <<def_detached_HEAD,detached HEAD>>, in which case it may - reference an arbitrary commit. + <<def_detached_HEAD,detached HEAD>>, in which case it directly + references an arbitrary commit. [[def_head_ref]]head ref:: A synonym for <<def_head,head>>. @@ -382,8 +382,9 @@ should not be combined with other pathspec. [[def_ref]]ref:: A 40-byte hex representation of a <<def_SHA1,SHA1>> or a name that - denotes a particular <<def_object,object>>. These may be stored in - `$GIT_DIR/refs/`. + denotes a particular <<def_object,object>>. They may be stored in + a file under `$GIT_DIR/refs/` directory, or + in the `$GIT_DIR/packed-refs` file. [[def_reflog]]reflog:: A reflog shows the local "history" of a ref. In other words, @@ -459,14 +460,14 @@ should not be combined with other pathspec. command. [[def_tag]]tag:: - A <<def_ref,ref>> pointing to a <<def_tag_object,tag>> or - <<def_commit_object,commit object>>. In contrast to a <<def_head,head>>, - a tag is not changed by a <<def_commit,commit>>. Tags (not - <<def_tag_object,tag objects>>) are stored in `$GIT_DIR/refs/tags/`. A - git tag has nothing to do with a Lisp tag (which would be - called an <<def_object_type,object type>> in git's context). A - tag is most typically used to mark a particular point in the - commit ancestry <<def_chain,chain>>. + A <<def_ref,ref>> under `refs/tags/` namespace that points to an + object of an arbitrary type (typically a tag points to either a + <<def_tag_object,tag>> or a <<def_commit_object,commit object>>). + In contrast to a <<def_head,head>>, a tag is not updated by + the `commit` command. A git tag has nothing to do with a Lisp + tag (which would be called an <<def_object_type,object type>> + in git's context). A tag is most typically used to mark a particular + point in the commit ancestry <<def_chain,chain>>. [[def_tag_object]]tag object:: An <<def_object,object>> containing a <<def_ref,ref>> pointing to diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 62340a5e4..39e620726 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -313,7 +313,7 @@ that you are filtering for a file `foo` in this commit graph: \ / / / / `-------------' ----------------------------------------------------------------------- -The horizontal line of history A--P is taken to be the first parent of +The horizontal line of history A---P is taken to be the first parent of each merge. The commits are: * `I` is the initial commit, in which `foo` exists with contents diff --git a/Documentation/technical/api-builtin.txt b/Documentation/technical/api-builtin.txt index 5cb2b0590..b0cafe87b 100644 --- a/Documentation/technical/api-builtin.txt +++ b/Documentation/technical/api-builtin.txt @@ -49,6 +49,8 @@ Additionally, if `foo` is a new command, there are 3 more things to do: . Add an entry for `git-foo` to `command-list.txt`. +. Add an entry for `/git-foo` to `.gitignore`. + How a built-in is called ------------------------ diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt index 369f91d3b..a7004c63e 100644 --- a/Documentation/technical/pack-protocol.txt +++ b/Documentation/technical/pack-protocol.txt @@ -179,34 +179,36 @@ and descriptions. Packfile Negotiation -------------------- -After reference and capabilities discovery, the client can decide -to terminate the connection by sending a flush-pkt, telling the -server it can now gracefully terminate (as happens with the ls-remote -command) or it can enter the negotiation phase, where the client and -server determine what the minimal packfile necessary for transport is. - -Once the client has the initial list of references that the server -has, as well as the list of capabilities, it will begin telling the -server what objects it wants and what objects it has, so the server -can make a packfile that only contains the objects that the client needs. -The client will also send a list of the capabilities it wants to be in -effect, out of what the server said it could do with the first 'want' line. +After reference and capabilities discovery, the client can decide to +terminate the connection by sending a flush-pkt, telling the server it can +now gracefully terminate, and disconnect, when it does not need any pack +data. This can happen with the ls-remote command, and also can happen when +the client already is up-to-date. + +Otherwise, it enters the negotiation phase, where the client and +server determine what the minimal packfile necessary for transport is, +by telling the server what objects it wants, its shallow objects +(if any), and the maximum commit depth it wants (if any). The client +will also send a list of the capabilities it wants to be in effect, +out of what the server said it could do with the first 'want' line. ---- upload-request = want-list - have-list - compute-end + *shallow-line + *1depth-request + flush-pkt want-list = first-want *additional-want - flush-pkt + + shallow-line = PKT_LINE("shallow" SP obj-id) + + depth-request = PKT_LINE("deepen" SP depth) first-want = PKT-LINE("want" SP obj-id SP capability-list LF) additional-want = PKT-LINE("want" SP obj-id LF) - have-list = *have-line - have-line = PKT-LINE("have" SP obj-id LF) - compute-end = flush-pkt / PKT-LINE("done") + depth = 1*DIGIT ---- Clients MUST send all the obj-ids it wants from the reference @@ -215,21 +217,64 @@ discovery phase as 'want' lines. Clients MUST send at least one obj-id in a 'want' command which did not appear in the response obtained through ref discovery. -If client is requesting a shallow clone, it will now send a 'deepen' -line with the depth it is requesting. +The client MUST write all obj-ids which it only has shallow copies +of (meaning that it does not have the parents of a commit) as +'shallow' lines so that the server is aware of the limitations of +the client's history. Clients MUST NOT mention an obj-id which +it does not know exists on the server. + +The client now sends the maximum commit history depth it wants for +this transaction, which is the number of commits it wants from the +tip of the history, if any, as a 'deepen' line. A depth of 0 is the +same as not making a depth request. The client does not want to receive +any commits beyond this depth, nor objects needed only to complete +those commits. Commits whose parents are not received as a result are +defined as shallow and marked as such in the server. This information +is sent back to the client in the next step. + +Once all the 'want's and 'shallow's (and optional 'deepen') are +transferred, clients MUST send a flush-pkt, to tell the server side +that it is done sending the list. + +Otherwise, if the client sent a positive depth request, the server +will determine which commits will and will not be shallow and +send this information to the client. If the client did not request +a positive depth, this step is skipped. -Once all the "want"s (and optional 'deepen') are transferred, -clients MUST send a flush-pkt. If the client has all the references -on the server, client flushes and disconnects. +---- + shallow-update = *shallow-line + *unshallow-line + flush-pkt -TODO: shallow/unshallow response and document the deepen command in the ABNF. + shallow-line = PKT-LINE("shallow" SP obj-id) + + unshallow-line = PKT-LINE("unshallow" SP obj-id) +---- + +If the client has requested a positive depth, the server will compute +the set of commits which are no deeper than the desired depth, starting +at the client's wants. The server writes 'shallow' lines for each +commit whose parents will not be sent as a result. The server writes +an 'unshallow' line for each commit which the client has indicated is +shallow, but is no longer shallow at the currently requested depth +(that is, its parents will now be sent). The server MUST NOT mark +as unshallow anything which the client has not indicated was shallow. Now the client will send a list of the obj-ids it has using 'have' -lines. In multi_ack mode, the canonical implementation will send up -to 32 of these at a time, then will send a flush-pkt. The canonical -implementation will skip ahead and send the next 32 immediately, -so that there is always a block of 32 "in-flight on the wire" at a -time. +lines, so the server can make a packfile that only contains the objects +that the client needs. In multi_ack mode, the canonical implementation +will send up to 32 of these at a time, then will send a flush-pkt. The +canonical implementation will skip ahead and send the next 32 immediately, +so that there is always a block of 32 "in-flight on the wire" at a time. + +---- + upload-haves = have-list + compute-end + + have-list = *have-line + have-line = PKT-LINE("have" SP obj-id LF) + compute-end = flush-pkt / PKT-LINE("done") +---- If the server reads 'have' lines, it then will respond by ACKing any of the obj-ids the client said it had that the server also has. The diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f04e4b138..8270abb5f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.7.6 +DEF_VER=v1.7.6.GIT LF=' ' @@ -25,6 +25,19 @@ set up install paths (via config.mak.autogen), so you can write instead $ make all doc ;# as yourself # make install install-doc install-html;# as root +If you're willing to trade off (much) longer build time for a later +faster git you can also do a profile feedback build with + + $ make profile-all + # make prefix=... install + +This will run the complete test suite as training workload and then +rebuild git with the generated profile feedback. This results in a git +which is a few percent faster on CPU intensive workloads. This +may be a good tradeoff for distribution packagers. + +Note that the profile feedback build stage currently generates +a lot of additional compiler warnings. Issues of note: @@ -115,6 +115,10 @@ all:: # # Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin). # +# Define NEEDS_SSL_WITH_CURL if you need -lssl with -lcurl (Minix). +# +# Define NEEDS_IDN_WITH_CURL if you need -lidn when using -lcurl (Minix). +# # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, @@ -153,6 +157,9 @@ all:: # that tells runtime paths to dynamic libraries; # "-Wl,-rpath=/path/lib" is used instead. # +# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback, +# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299) +# # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely @@ -556,6 +563,7 @@ LIB_H += sha1-lookup.h LIB_H += sideband.h LIB_H += sigchain.h LIB_H += strbuf.h +LIB_H += streaming.h LIB_H += string-list.h LIB_H += submodule.h LIB_H += tag.h @@ -662,6 +670,7 @@ LIB_OBJS += shallow.o LIB_OBJS += sideband.o LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o +LIB_OBJS += streaming.o LIB_OBJS += string-list.o LIB_OBJS += submodule.o LIB_OBJS += symlinks.o @@ -1124,8 +1133,6 @@ endif X = .exe endif ifeq ($(uname_S),Interix) - NO_SYS_POLL_H = YesPlease - NO_INTTYPES_H = YesPlease NO_INITGROUPS = YesPlease NO_IPV6 = YesPlease NO_MEMMEM = YesPlease @@ -1136,12 +1143,30 @@ ifeq ($(uname_S),Interix) ifeq ($(uname_R),3.5) NO_INET_NTOP = YesPlease NO_INET_PTON = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease endif ifeq ($(uname_R),5.2) NO_INET_NTOP = YesPlease NO_INET_PTON = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_FNMATCH_CASEFOLD = YesPlease endif endif +ifeq ($(uname_S),Minix) + NO_IPV6 = YesPlease + NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + NO_NSEC = YesPlease + NEEDS_LIBGEN = + NEEDS_CRYPTO_WITH_SSL = YesPlease + NEEDS_IDN_WITH_CURL = YesPlease + NEEDS_SSL_WITH_CURL = YesPlease + NEEDS_RESOLV = + NO_HSTRERROR = YesPlease + NO_MMAP = YesPlease + NO_CURL = + NO_EXPAT = +endif ifneq (,$(findstring MINGW,$(uname_S))) pathsep = ; NO_PREAD = YesPlease @@ -1286,6 +1311,16 @@ else else CURL_LIBCURL = -lcurl endif + ifdef NEEDS_SSL_WITH_CURL + CURL_LIBCURL += -lssl + ifdef NEEDS_CRYPTO_WITH_SSL + CURL_LIBCURL += -lcrypto + endif + endif + ifdef NEEDS_IDN_WITH_CURL + CURL_LIBCURL += -lidn + endif + REMOTE_CURL_PRIMARY = git-remote-http$X REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES) @@ -1322,7 +1357,7 @@ ifndef NO_OPENSSL OPENSSL_LINK = endif ifdef NEEDS_CRYPTO_WITH_SSL - OPENSSL_LINK += -lcrypto + OPENSSL_LIBSSL += -lcrypto endif else BASIC_CFLAGS += -DNO_OPENSSL @@ -1374,6 +1409,9 @@ endif ifdef USE_ST_TIMESPEC BASIC_CFLAGS += -DUSE_ST_TIMESPEC endif +ifdef NO_NORETURN + BASIC_CFLAGS += -DNO_NORETURN +endif ifdef NO_NSEC BASIC_CFLAGS += -DNO_NSEC endif @@ -1706,7 +1744,7 @@ git.sp git.s git.o: EXTRA_CPPFLAGS = -DGIT_VERSION='"$(GIT_VERSION)"' \ '-DGIT_MAN_PATH="$(mandir_SQ)"' \ '-DGIT_INFO_PATH="$(infodir_SQ)"' -git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) +git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) @@ -2004,17 +2042,17 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \ -DNDEBUG -DOVERRIDE_STRDUP -DREPLACE_SYSTEM_ALLOCATOR endif -git-%$X: %.o $(GITLIBS) +git-%$X: %.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) -git-imap-send$X: imap-send.o $(GITLIBS) +git-imap-send$X: imap-send.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO) -git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) +git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o GIT-LDFLAGS $(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) +git-http-push$X: revision.o http.o http-push.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) @@ -2024,7 +2062,7 @@ $(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY) ln -s $< $@ 2>/dev/null || \ cp $< $@ -$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS) +$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) @@ -2094,6 +2132,15 @@ GIT-CFLAGS: FORCE echo "$$FLAGS" >GIT-CFLAGS; \ fi +TRACK_LDFLAGS = $(subst ','\'',$(ALL_LDFLAGS)) + +GIT-LDFLAGS: FORCE + @FLAGS='$(TRACK_LDFLAGS)'; \ + if test x"$$FLAGS" != x"`cat GIT-LDFLAGS 2>/dev/null`" ; then \ + echo 1>&2 " * new link flags"; \ + echo "$$FLAGS" >GIT-LDFLAGS; \ + fi + # We need to apply sq twice, once to protect from the shell # that runs GIT-BUILD-OPTIONS, and then again to protect it # and the first level quoting from the shell that runs "echo". @@ -2165,7 +2212,7 @@ test-svn-fe$X: vcs-svn/lib.a .PRECIOUS: $(TEST_OBJS) -test-%$X: test-%.o $(GITLIBS) +test-%$X: test-%.o GIT-LDFLAGS $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS) check-sha1:: test-sha1$X @@ -2375,7 +2422,7 @@ ifndef NO_TCLTK $(MAKE) -C gitk-git clean $(MAKE) -C git-gui clean endif - $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS + $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-LDFLAGS GIT-GUI-VARS GIT-BUILD-OPTIONS .PHONY: all install clean strip .PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell @@ -2486,3 +2533,19 @@ cover_db: coverage-report cover_db_html: cover_db cover -report html -outputdir cover_db_html cover_db + +### profile feedback build +# +.PHONY: profile-all profile-clean + +PROFILE_GEN_CFLAGS := $(CFLAGS) -fprofile-generate -DNO_NORETURN=1 +PROFILE_USE_CFLAGS := $(CFLAGS) -fprofile-use -fprofile-correction -DNO_NORETURN=1 + +profile-clean: + $(RM) $(addsuffix *.gcda,$(object_dirs)) + $(RM) $(addsuffix *.gcno,$(object_dirs)) + +profile-all: profile-clean + $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" all + $(MAKE) CFLAGS="$(PROFILE_GEN_CFLAGS)" -j1 test + $(MAKE) CFLAGS="$(PROFILE_USE_CFLAGS)" all @@ -1 +1 @@ -Documentation/RelNotes/1.7.6.txt
\ No newline at end of file +Documentation/RelNotes/1.7.7.txt
\ No newline at end of file @@ -40,7 +40,7 @@ const char *real_path(const char *path) while (depth--) { if (!is_directory(buf)) { - char *last_slash = strrchr(buf, '/'); + char *last_slash = find_last_dir_sep(buf); if (last_slash) { *last_slash = '\0'; last_elem = xstrdup(last_slash + 1); @@ -65,7 +65,7 @@ const char *real_path(const char *path) if (len + strlen(last_elem) + 2 > PATH_MAX) die ("Too long path name: '%s/%s'", buf, last_elem); - if (len && buf[len-1] != '/') + if (len && !is_dir_sep(buf[len-1])) buf[len++] = '/'; strcpy(buf + len, last_elem); free(last_elem); @@ -102,7 +102,8 @@ static const char *get_pwd_cwd(void) pwd = getenv("PWD"); if (pwd && strcmp(pwd, cwd)) { stat(cwd, &cwd_stat); - if (!stat(pwd, &pwd_stat) && + if ((cwd_stat.st_dev || cwd_stat.st_ino) && + !stat(pwd, &pwd_stat) && pwd_stat.st_dev == cwd_stat.st_dev && pwd_stat.st_ino == cwd_stat.st_ino) { strlcpy(cwd, pwd, PATH_MAX); diff --git a/archive-tar.c b/archive-tar.c index cee06ce3c..20af0051a 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -4,6 +4,7 @@ #include "cache.h" #include "tar.h" #include "archive.h" +#include "run-command.h" #define RECORDSIZE (512) #define BLOCKSIZE (RECORDSIZE * 20) @@ -13,6 +14,9 @@ static unsigned long offset; static int tar_umask = 002; +static int write_tar_filter_archive(const struct archiver *ar, + struct archiver_args *args); + /* writes out the whole block, but only if it is full */ static void write_if_needed(void) { @@ -220,6 +224,67 @@ static int write_global_extended_header(struct archiver_args *args) return err; } +static struct archiver **tar_filters; +static int nr_tar_filters; +static int alloc_tar_filters; + +static struct archiver *find_tar_filter(const char *name, int len) +{ + int i; + for (i = 0; i < nr_tar_filters; i++) { + struct archiver *ar = tar_filters[i]; + if (!strncmp(ar->name, name, len) && !ar->name[len]) + return ar; + } + return NULL; +} + +static int tar_filter_config(const char *var, const char *value, void *data) +{ + struct archiver *ar; + const char *dot; + const char *name; + const char *type; + int namelen; + + if (prefixcmp(var, "tar.")) + return 0; + dot = strrchr(var, '.'); + if (dot == var + 9) + return 0; + + name = var + 4; + namelen = dot - name; + type = dot + 1; + + ar = find_tar_filter(name, namelen); + if (!ar) { + ar = xcalloc(1, sizeof(*ar)); + ar->name = xmemdupz(name, namelen); + ar->write_archive = write_tar_filter_archive; + ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS; + ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters); + tar_filters[nr_tar_filters++] = ar; + } + + if (!strcmp(type, "command")) { + if (!value) + return config_error_nonbool(var); + free(ar->data); + ar->data = xstrdup(value); + return 0; + } + if (!strcmp(type, "remote")) { + if (git_config_bool(var, value)) + ar->flags |= ARCHIVER_REMOTE; + else + ar->flags &= ~ARCHIVER_REMOTE; + return 0; + } + + return 0; +} + static int git_tar_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "tar.umask")) { @@ -231,15 +296,15 @@ static int git_tar_config(const char *var, const char *value, void *cb) } return 0; } - return git_default_config(var, value, cb); + + return tar_filter_config(var, value, cb); } -int write_tar_archive(struct archiver_args *args) +static int write_tar_archive(const struct archiver *ar, + struct archiver_args *args) { int err = 0; - git_config(git_tar_config, NULL); - if (args->commit_sha1) err = write_global_extended_header(args); if (!err) @@ -248,3 +313,65 @@ int write_tar_archive(struct archiver_args *args) write_trailer(); return err; } + +static int write_tar_filter_archive(const struct archiver *ar, + struct archiver_args *args) +{ + struct strbuf cmd = STRBUF_INIT; + struct child_process filter; + const char *argv[2]; + int r; + + if (!ar->data) + die("BUG: tar-filter archiver called with no filter defined"); + + strbuf_addstr(&cmd, ar->data); + if (args->compression_level >= 0) + strbuf_addf(&cmd, " -%d", args->compression_level); + + memset(&filter, 0, sizeof(filter)); + argv[0] = cmd.buf; + argv[1] = NULL; + filter.argv = argv; + filter.use_shell = 1; + filter.in = -1; + + if (start_command(&filter) < 0) + die_errno("unable to start '%s' filter", argv[0]); + close(1); + if (dup2(filter.in, 1) < 0) + die_errno("unable to redirect descriptor"); + close(filter.in); + + r = write_tar_archive(ar, args); + + close(1); + if (finish_command(&filter) != 0) + die("'%s' filter reported error", argv[0]); + + strbuf_release(&cmd); + return r; +} + +static struct archiver tar_archiver = { + "tar", + write_tar_archive, + ARCHIVER_REMOTE +}; + +void init_tar_archiver(void) +{ + int i; + register_archiver(&tar_archiver); + + tar_filter_config("tar.tgz.command", "gzip -cn", NULL); + tar_filter_config("tar.tgz.remote", "true", NULL); + tar_filter_config("tar.tar.gz.command", "gzip -cn", NULL); + tar_filter_config("tar.tar.gz.remote", "true", NULL); + git_config(git_tar_config, NULL); + for (i = 0; i < nr_tar_filters; i++) { + /* omit any filters that never had a command configured */ + if (tar_filters[i]->data) + register_archiver(tar_filters[i]); + } +} diff --git a/archive-zip.c b/archive-zip.c index cf285044e..02d1f3787 100644 --- a/archive-zip.c +++ b/archive-zip.c @@ -90,14 +90,14 @@ static void copy_le32(unsigned char *dest, unsigned int n) static void *zlib_deflate(void *data, unsigned long size, int compression_level, unsigned long *compressed_size) { - z_stream stream; + git_zstream stream; unsigned long maxsize; void *buffer; int result; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, compression_level); - maxsize = deflateBound(&stream, size); + git_deflate_init(&stream, compression_level); + maxsize = git_deflate_bound(&stream, size); buffer = xmalloc(maxsize); stream.next_in = data; @@ -106,7 +106,7 @@ static void *zlib_deflate(void *data, unsigned long size, stream.avail_out = maxsize; do { - result = deflate(&stream, Z_FINISH); + result = git_deflate(&stream, Z_FINISH); } while (result == Z_OK); if (result != Z_STREAM_END) { @@ -114,7 +114,7 @@ static void *zlib_deflate(void *data, unsigned long size, return NULL; } - deflateEnd(&stream); + git_deflate_end(&stream); *compressed_size = stream.total_out; return buffer; @@ -261,7 +261,8 @@ static void dos_time(time_t *time, int *dos_date, int *dos_time) *dos_time = t->tm_sec / 2 + t->tm_min * 32 + t->tm_hour * 2048; } -int write_zip_archive(struct archiver_args *args) +static int write_zip_archive(const struct archiver *ar, + struct archiver_args *args) { int err; @@ -278,3 +279,14 @@ int write_zip_archive(struct archiver_args *args) return err; } + +static struct archiver zip_archiver = { + "zip", + write_zip_archive, + ARCHIVER_WANT_COMPRESSION_LEVELS|ARCHIVER_REMOTE +}; + +void init_zip_archiver(void) +{ + register_archiver(&zip_archiver); +} @@ -14,16 +14,15 @@ static char const * const archive_usage[] = { NULL }; -#define USES_ZLIB_COMPRESSION 1 - -static const struct archiver { - const char *name; - write_archive_fn_t write_archive; - unsigned int flags; -} archivers[] = { - { "tar", write_tar_archive }, - { "zip", write_zip_archive, USES_ZLIB_COMPRESSION }, -}; +static const struct archiver **archivers; +static int nr_archivers; +static int alloc_archivers; + +void register_archiver(struct archiver *ar) +{ + ALLOC_GROW(archivers, nr_archivers + 1, alloc_archivers); + archivers[nr_archivers++] = ar; +} static void format_subst(const struct commit *commit, const char *src, size_t len, @@ -208,9 +207,9 @@ static const struct archiver *lookup_archiver(const char *name) if (!name) return NULL; - for (i = 0; i < ARRAY_SIZE(archivers); i++) { - if (!strcmp(name, archivers[i].name)) - return &archivers[i]; + for (i = 0; i < nr_archivers; i++) { + if (!strcmp(name, archivers[i]->name)) + return archivers[i]; } return NULL; } @@ -299,9 +298,10 @@ static void parse_treeish_arg(const char **argv, PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, NULL, (p) } static int parse_archive_args(int argc, const char **argv, - const struct archiver **ar, struct archiver_args *args) + const struct archiver **ar, struct archiver_args *args, + const char *name_hint, int is_remote) { - const char *format = "tar"; + const char *format = NULL; const char *base = NULL; const char *remote = NULL; const char *exec = NULL; @@ -355,21 +355,27 @@ static int parse_archive_args(int argc, const char **argv, base = ""; if (list) { - for (i = 0; i < ARRAY_SIZE(archivers); i++) - printf("%s\n", archivers[i].name); + for (i = 0; i < nr_archivers; i++) + if (!is_remote || archivers[i]->flags & ARCHIVER_REMOTE) + printf("%s\n", archivers[i]->name); exit(0); } + if (!format && name_hint) + format = archive_format_from_filename(name_hint); + if (!format) + format = "tar"; + /* We need at least one parameter -- tree-ish */ if (argc < 1) usage_with_options(archive_usage, opts); *ar = lookup_archiver(format); - if (!*ar) + if (!*ar || (is_remote && !((*ar)->flags & ARCHIVER_REMOTE))) die("Unknown archive format '%s'", format); args->compression_level = Z_DEFAULT_COMPRESSION; if (compression_level != -1) { - if ((*ar)->flags & USES_ZLIB_COMPRESSION) + if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS) args->compression_level = compression_level; else { die("Argument not supported for format '%s': -%d", @@ -385,19 +391,55 @@ static int parse_archive_args(int argc, const char **argv, } int write_archive(int argc, const char **argv, const char *prefix, - int setup_prefix) + int setup_prefix, const char *name_hint, int remote) { + int nongit = 0; const struct archiver *ar = NULL; struct archiver_args args; - argc = parse_archive_args(argc, argv, &ar, &args); if (setup_prefix && prefix == NULL) - prefix = setup_git_directory(); + prefix = setup_git_directory_gently(&nongit); + + git_config(git_default_config, NULL); + init_tar_archiver(); + init_zip_archiver(); + + argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote); + if (nongit) { + /* + * We know this will die() with an error, so we could just + * die ourselves; but its error message will be more specific + * than what we could write here. + */ + setup_git_directory(); + } parse_treeish_arg(argv, &args, prefix); parse_pathspec_arg(argv + 1, &args); - git_config(git_default_config, NULL); + return ar->write_archive(ar, &args); +} - return ar->write_archive(&args); +static int match_extension(const char *filename, const char *ext) +{ + int prefixlen = strlen(filename) - strlen(ext); + + /* + * We need 1 character for the '.', and 1 character to ensure that the + * prefix is non-empty (k.e., we don't match .tar.gz with no actual + * filename). + */ + if (prefixlen < 2 || filename[prefixlen-1] != '.') + return 0; + return !strcmp(filename + prefixlen, ext); +} + +const char *archive_format_from_filename(const char *filename) +{ + int i; + + for (i = 0; i < nr_archivers; i++) + if (match_extension(filename, archivers[i]->name)) + return archivers[i]->name; + return NULL; } @@ -14,17 +14,24 @@ struct archiver_args { int compression_level; }; -typedef int (*write_archive_fn_t)(struct archiver_args *); +#define ARCHIVER_WANT_COMPRESSION_LEVELS 1 +#define ARCHIVER_REMOTE 2 +struct archiver { + const char *name; + int (*write_archive)(const struct archiver *, struct archiver_args *); + unsigned flags; + void *data; +}; +extern void register_archiver(struct archiver *); -typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size); +extern void init_tar_archiver(void); +extern void init_zip_archiver(void); -/* - * Archive-format specific backends. - */ -extern int write_tar_archive(struct archiver_args *); -extern int write_zip_archive(struct archiver_args *); +typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const unsigned char *sha1, const char *path, size_t pathlen, unsigned int mode, void *buffer, unsigned long size); extern int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry); -extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix); +extern int write_archive(int argc, const char **argv, const char *prefix, int setup_prefix, const char *name_hint, int remote); + +const char *archive_format_from_filename(const char *filename); #endif /* ARCHIVE_H */ diff --git a/builtin/apply.c b/builtin/apply.c index 530d4bb7e..f2edc5281 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1634,7 +1634,7 @@ static inline int metadata_changes(struct patch *patch) static char *inflate_it(const void *data, unsigned long size, unsigned long inflated_size) { - z_stream stream; + git_zstream stream; void *out; int st; diff --git a/builtin/archive.c b/builtin/archive.c index b14eaba15..883c0092a 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -24,7 +24,8 @@ static void create_output_file(const char *output_file) } static int run_remote_archiver(int argc, const char **argv, - const char *remote, const char *exec) + const char *remote, const char *exec, + const char *name_hint) { char buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; @@ -37,6 +38,17 @@ static int run_remote_archiver(int argc, const char **argv, transport = transport_get(_remote, _remote->url[0]); transport_connect(transport, "git-upload-archive", exec, fd); + /* + * Inject a fake --format field at the beginning of the + * arguments, with the format inferred from our output + * filename. This way explicit --format options can override + * it. + */ + if (name_hint) { + const char *format = archive_format_from_filename(name_hint); + if (format) + packet_write(fd[1], "argument --format=%s\n", format); + } for (i = 1; i < argc; i++) packet_write(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); @@ -63,17 +75,6 @@ static int run_remote_archiver(int argc, const char **argv, return !!rv; } -static const char *format_from_name(const char *filename) -{ - const char *ext = strrchr(filename, '.'); - if (!ext) - return NULL; - ext++; - if (!strcasecmp(ext, "zip")) - return "--format=zip"; - return NULL; -} - #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ PARSE_OPT_KEEP_ARGV0 | \ PARSE_OPT_KEEP_UNKNOWN | \ @@ -84,7 +85,6 @@ int cmd_archive(int argc, const char **argv, const char *prefix) const char *exec = "git-upload-archive"; const char *output = NULL; const char *remote = NULL; - const char *format_option = NULL; struct option local_opts[] = { OPT_STRING('o', "output", &output, "file", "write the archive to this file"), @@ -98,32 +98,13 @@ int cmd_archive(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); - if (output) { + if (output) create_output_file(output); - format_option = format_from_name(output); - } - - /* - * We have enough room in argv[] to muck it in place, because - * --output must have been given on the original command line - * if we get to this point, and parse_options() must have eaten - * it, i.e. we can add back one element to the array. - * - * We add a fake --format option at the beginning, with the - * format inferred from our output filename. This way explicit - * --format options can override it, and the fake option is - * inserted before any "--" that might have been given. - */ - if (format_option) { - memmove(argv + 2, argv + 1, sizeof(*argv) * argc); - argv[1] = format_option; - argv[++argc] = NULL; - } if (remote) - return run_remote_archiver(argc, argv, remote, exec); + return run_remote_archiver(argc, argv, remote, exec, output); setvbuf(stderr, NULL, _IOLBF, BUFSIZ); - return write_archive(argc, argv, prefix, 1); + return write_archive(argc, argv, prefix, 1, output, 0); } diff --git a/builtin/checkout.c b/builtin/checkout.c index 28cdc51b8..d647a3130 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -657,24 +657,25 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs) "Warning: you are leaving %d commit behind, " "not connected to\n" "any of your branches:\n\n" - "%s\n" - "If you want to keep it by creating a new branch, " - "this may be a good time\nto do so with:\n\n" - " git branch new_branch_name %s\n\n", + "%s\n", /* The plural version */ "Warning: you are leaving %d commits behind, " "not connected to\n" "any of your branches:\n\n" - "%s\n" - "If you want to keep them by creating a new branch, " - "this may be a good time\nto do so with:\n\n" - " git branch new_branch_name %s\n\n", + "%s\n", /* Give ngettext() the count */ lost), lost, - sb.buf, - sha1_to_hex(commit->object.sha1)); + sb.buf); strbuf_release(&sb); + + if (advice_detached_head) + fprintf(stderr, + _( + "If you want to keep them by creating a new branch, " + "this may be a good time\nto do so with:\n\n" + " git branch new_branch_name %s\n\n"), + sha1_to_hex(commit->object.sha1)); } /* @@ -715,10 +716,12 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) unsigned char rev[20]; int flag; memset(&old, 0, sizeof(old)); - old.path = resolve_ref("HEAD", rev, 0, &flag); + old.path = xstrdup(resolve_ref("HEAD", rev, 0, &flag)); old.commit = lookup_commit_reference_gently(rev, 1); - if (!(flag & REF_ISSYMREF)) + if (!(flag & REF_ISSYMREF)) { + free((char *)old.path); old.path = NULL; + } if (old.path && !prefixcmp(old.path, "refs/heads/")) old.name = old.path + strlen("refs/heads/"); @@ -741,6 +744,7 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) update_refs_for_switch(opts, &old, new); ret = post_checkout_hook(old.commit, new->commit, 1); + free((char *)old.path); return ret || opts->writeout_error; } @@ -1071,7 +1075,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) if (strbuf_check_branch_ref(&buf, opts.new_branch)) die(_("git checkout: we do not like '%s' as a branch name."), opts.new_branch); - if (!get_sha1(buf.buf, rev)) { + if (ref_exists(buf.buf)) { opts.branch_exists = 1; if (!opts.new_branch_force) die(_("git checkout: branch %s already exists"), diff --git a/builtin/clone.c b/builtin/clone.c index f579794d9..7663bc22c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -46,6 +46,7 @@ static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress; +static struct string_list option_config; static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), @@ -83,7 +84,8 @@ static struct option builtin_clone_options[] = { "create a shallow clone of that depth"), OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", "separate git dir from working tree"), - + OPT_STRING_LIST('c', "config", &option_config, "key=value", + "set config inside the new repository"), OPT_END() }; @@ -343,8 +345,9 @@ static void remove_junk_on_signal(int signo) static struct ref *wanted_peer_refs(const struct ref *refs, struct refspec *refspec) { - struct ref *local_refs = NULL; - struct ref **tail = &local_refs; + struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD")); + struct ref *local_refs = head; + struct ref **tail = head ? &head->next : &local_refs; get_fetch_map(refs, refspec, &tail, 0); if (!option_mirror) @@ -357,13 +360,32 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; - for (r = local_refs; r; r = r->next) + for (r = local_refs; r; r = r->next) { + if (!r->peer_ref) + continue; add_extra_ref(r->peer_ref->name, r->old_sha1, 0); + } pack_refs(PACK_REFS_ALL); clear_extra_refs(); } +static int write_one_config(const char *key, const char *value, void *data) +{ + return git_config_set_multivar(key, value ? value : "true", "^$", 0); +} + +static void write_config(struct string_list *config) +{ + int i; + + for (i = 0; i < config->nr; i++) { + if (git_config_parse_parameter(config->items[i].string, + write_one_config, NULL) < 0) + die("unable to write parameters to config file"); + } +} + int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; @@ -482,6 +504,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) printf(_("Cloning into %s...\n"), dir); } init_db(option_template, INIT_DB_QUIET); + write_config(&option_config); /* * At this point, the config exists, so we do not need the diff --git a/builtin/fast-export.c b/builtin/fast-export.c index daf19451b..becef8578 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -26,6 +26,7 @@ static int progress; static enum { ABORT, VERBATIM, WARN, STRIP } signed_tag_mode = ABORT; static enum { ERROR, DROP, REWRITE } tag_of_filtered_mode = ABORT; static int fake_missing_tagger; +static int use_done_feature; static int no_data; static int full_tree; @@ -627,6 +628,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) "Fake a tagger when tags lack one"), OPT_BOOLEAN(0, "full-tree", &full_tree, "Output full tree for each commit"), + OPT_BOOLEAN(0, "use-done-feature", &use_done_feature, + "Use the done feature to terminate the stream"), { OPTION_NEGBIT, 0, "data", &no_data, NULL, "Skip output of blob data", PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, @@ -648,6 +651,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (argc > 1) usage_with_options (fast_export_usage, options); + if (use_done_feature) + printf("feature done\n"); + if (import_filename) import_marks(import_filename); @@ -675,5 +681,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (export_filename) export_marks(export_filename); + if (use_done_feature) + printf("done\n"); + return 0; } diff --git a/builtin/grep.c b/builtin/grep.c index 871afaa3c..cccf8da6d 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -93,8 +93,7 @@ static pthread_cond_t cond_write; /* Signalled when we are finished with everything. */ static pthread_cond_t cond_result; -static int print_hunk_marks_between_files; -static int printed_something; +static int skip_first_line; static void add_work(enum work_type type, char *name, void *id) { @@ -160,10 +159,20 @@ static void work_done(struct work_item *w) todo_done = (todo_done+1) % ARRAY_SIZE(todo)) { w = &todo[todo_done]; if (w->out.len) { - if (print_hunk_marks_between_files && printed_something) - write_or_die(1, "--\n", 3); - write_or_die(1, w->out.buf, w->out.len); - printed_something = 1; + const char *p = w->out.buf; + size_t len = w->out.len; + + /* Skip the leading hunk mark of the first file. */ + if (skip_first_line) { + while (len) { + len--; + if (*p++ == '\n') + break; + } + skip_first_line = 0; + } + + write_or_die(1, p, len); } free(w->name); free(w->identifier); @@ -813,6 +822,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('c', "count", &opt.count, "show the number of matches instead of matching lines"), OPT__COLOR(&opt.color, "highlight matches"), + OPT_BOOLEAN(0, "break", &opt.file_break, + "print empty line between matches from different files"), + OPT_BOOLEAN(0, "heading", &opt.heading, + "show filename only once above matches from same file"), OPT_GROUP(""), OPT_CALLBACK('C', NULL, &opt, "n", "show <n> context lines before and after matches", @@ -967,8 +980,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) use_threads = 0; if (use_threads) { - if (opt.pre_context || opt.post_context) - print_hunk_marks_between_files = 1; + if (opt.pre_context || opt.post_context || opt.file_break) + skip_first_line = 1; start_threads(&opt); } #else diff --git a/builtin/index-pack.c b/builtin/index-pack.c index e40451ffb..0945adbb3 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -11,7 +11,7 @@ #include "exec_cmd.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [ --keep | --keep=<msg> ] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; +"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; struct object_entry { struct pack_idx_entry idx; @@ -19,6 +19,8 @@ struct object_entry { unsigned int hdr_size; enum object_type type; enum object_type real_type; + unsigned delta_depth; + int base_object_no; }; union delta_base { @@ -66,6 +68,7 @@ static struct progress *progress; static unsigned char input_buffer[4096]; static unsigned int input_offset, input_len; static off_t consumed_bytes; +static unsigned deepest_delta; static git_SHA_CTX input_ctx; static uint32_t input_crc32; static int input_fd, output_fd, pack_fd; @@ -265,7 +268,7 @@ static void unlink_base_data(struct base_data *c) static void *unpack_entry_data(unsigned long offset, unsigned long size) { int status; - z_stream stream; + git_zstream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); @@ -355,7 +358,7 @@ static void *get_data_from_pack(struct object_entry *obj) off_t from = obj[0].idx.offset + obj[0].hdr_size; unsigned long len = obj[1].idx.offset - from; unsigned char *data, *inbuf; - z_stream stream; + git_zstream stream; int status; data = xmalloc(obj->size); @@ -389,7 +392,18 @@ static void *get_data_from_pack(struct object_entry *obj) return data; } -static int find_delta(const union delta_base *base) +static int compare_delta_bases(const union delta_base *base1, + const union delta_base *base2, + enum object_type type1, + enum object_type type2) +{ + int cmp = type1 - type2; + if (cmp) + return cmp; + return memcmp(base1, base2, UNION_BASE_SZ); +} + +static int find_delta(const union delta_base *base, enum object_type type) { int first = 0, last = nr_deltas; @@ -398,7 +412,8 @@ static int find_delta(const union delta_base *base) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base, &delta->base, UNION_BASE_SZ); + cmp = compare_delta_bases(base, &delta->base, + type, objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -411,9 +426,10 @@ static int find_delta(const union delta_base *base) } static void find_delta_children(const union delta_base *base, - int *first_index, int *last_index) + int *first_index, int *last_index, + enum object_type type) { - int first = find_delta(base); + int first = find_delta(base, type); int last = first; int end = nr_deltas - 1; @@ -483,12 +499,17 @@ static void sha1_object(const void *data, unsigned long size, } } +static int is_delta_type(enum object_type type) +{ + return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA); +} + static void *get_base_data(struct base_data *c) { if (!c->data) { struct object_entry *obj = c->obj; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + if (is_delta_type(obj->type)) { void *base = get_base_data(c->base); void *raw = get_data_from_pack(obj); c->data = patch_delta( @@ -515,6 +536,10 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data, *delta_data; delta_obj->real_type = base->obj->real_type; + delta_obj->delta_depth = base->obj->delta_depth + 1; + if (deepest_delta < delta_obj->delta_depth) + deepest_delta = delta_obj->delta_depth; + delta_obj->base_object_no = base->obj - objects; delta_data = get_data_from_pack(delta_obj); base_data = get_base_data(base); result->obj = delta_obj; @@ -541,11 +566,13 @@ static void find_unresolved_deltas(struct base_data *base, union delta_base base_spec; hashcpy(base_spec.sha1, base->obj->idx.sha1); - find_delta_children(&base_spec, &ref_first, &ref_last); + find_delta_children(&base_spec, + &ref_first, &ref_last, OBJ_REF_DELTA); memset(&base_spec, 0, sizeof(base_spec)); base_spec.offset = base->obj->idx.offset; - find_delta_children(&base_spec, &ofs_first, &ofs_last); + find_delta_children(&base_spec, + &ofs_first, &ofs_last, OBJ_OFS_DELTA); } if (ref_last == -1 && ofs_last == -1) { @@ -557,24 +584,24 @@ static void find_unresolved_deltas(struct base_data *base, for (i = ref_first; i <= ref_last; i++) { struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_REF_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ref_last && ofs_last == -1) - free_base_data(base); - find_unresolved_deltas(&result, base); - } + struct base_data result; + + assert(child->real_type == OBJ_REF_DELTA); + resolve_delta(child, base, &result); + if (i == ref_last && ofs_last == -1) + free_base_data(base); + find_unresolved_deltas(&result, base); } for (i = ofs_first; i <= ofs_last; i++) { struct object_entry *child = objects + deltas[i].obj_no; - if (child->real_type == OBJ_OFS_DELTA) { - struct base_data result; - resolve_delta(child, base, &result); - if (i == ofs_last) - free_base_data(base); - find_unresolved_deltas(&result, base); - } + struct base_data result; + + assert(child->real_type == OBJ_OFS_DELTA); + resolve_delta(child, base, &result); + if (i == ofs_last) + free_base_data(base); + find_unresolved_deltas(&result, base); } unlink_base_data(base); @@ -584,7 +611,11 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ); + + /* group by type (ref vs ofs) and then by value (sha-1 or offset) */ + return compare_delta_bases(&delta_a->base, &delta_b->base, + objects[delta_a->obj_no].type, + objects[delta_b->obj_no].type); } /* Parse all objects and return the pack content SHA1 hash */ @@ -608,7 +639,7 @@ static void parse_pack_objects(unsigned char *sha1) struct object_entry *obj = &objects[i]; void *data = unpack_raw_entry(obj, &delta->base); obj->real_type = obj->type; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) { + if (is_delta_type(obj->type)) { nr_deltas++; delta->obj_no = i; delta++; @@ -655,7 +686,7 @@ static void parse_pack_objects(unsigned char *sha1) struct object_entry *obj = &objects[i]; struct base_data base_obj; - if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) + if (is_delta_type(obj->type)) continue; base_obj.obj = obj; base_obj.data = NULL; @@ -666,26 +697,26 @@ static void parse_pack_objects(unsigned char *sha1) static int write_compressed(struct sha1file *f, void *in, unsigned int size) { - z_stream stream; + git_zstream stream; int status; unsigned char outbuf[4096]; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); + git_deflate_init(&stream, zlib_compression_level); stream.next_in = in; stream.avail_in = size; do { stream.next_out = outbuf; stream.avail_out = sizeof(outbuf); - status = deflate(&stream, Z_FINISH); + status = git_deflate(&stream, Z_FINISH); sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out); } while (status == Z_OK); if (status != Z_STREAM_END) die("unable to deflate appended object (%d)", status); size = stream.total_out; - deflateEnd(&stream); + git_deflate_end(&stream); return size; } @@ -859,24 +890,137 @@ static void final(const char *final_pack_name, const char *curr_pack_name, static int git_index_pack_config(const char *k, const char *v, void *cb) { + struct pack_idx_option *opts = cb; + if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) - die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); + opts->version = git_config_int(k, v); + if (opts->version > 2) + die("bad pack.indexversion=%"PRIu32, opts->version); return 0; } return git_default_config(k, v, cb); } +static int cmp_uint32(const void *a_, const void *b_) +{ + uint32_t a = *((uint32_t *)a_); + uint32_t b = *((uint32_t *)b_); + + return (a < b) ? -1 : (a != b); +} + +static void read_v2_anomalous_offsets(struct packed_git *p, + struct pack_idx_option *opts) +{ + const uint32_t *idx1, *idx2; + uint32_t i; + + /* The address of the 4-byte offset table */ + idx1 = (((const uint32_t *)p->index_data) + + 2 /* 8-byte header */ + + 256 /* fan out */ + + 5 * p->num_objects /* 20-byte SHA-1 table */ + + p->num_objects /* CRC32 table */ + ); + + /* The address of the 8-byte offset table */ + idx2 = idx1 + p->num_objects; + + for (i = 0; i < p->num_objects; i++) { + uint32_t off = ntohl(idx1[i]); + if (!(off & 0x80000000)) + continue; + off = off & 0x7fffffff; + if (idx2[off * 2]) + continue; + /* + * The real offset is ntohl(idx2[off * 2]) in high 4 + * octets, and ntohl(idx2[off * 2 + 1]) in low 4 + * octets. But idx2[off * 2] is Zero!!! + */ + ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc); + opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]); + } + + if (1 < opts->anomaly_nr) + qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32); +} + +static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) +{ + struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1); + + if (!p) + die("Cannot open existing pack file '%s'", pack_name); + if (open_pack_index(p)) + die("Cannot open existing pack idx file for '%s'", pack_name); + + /* Read the attributes from the existing idx file */ + opts->version = p->index_version; + + if (opts->version == 2) + read_v2_anomalous_offsets(p, opts); + + /* + * Get rid of the idx file as we do not need it anymore. + * NEEDSWORK: extract this bit from free_pack_by_name() in + * sha1_file.c, perhaps? It shouldn't matter very much as we + * know we haven't installed this pack (hence we never have + * read anything from it). + */ + close_pack_index(p); + free(p); +} + +static void show_pack_info(int stat_only) +{ + int i, baseobjects = nr_objects - nr_deltas; + unsigned long *chain_histogram = NULL; + + if (deepest_delta) + chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long)); + + for (i = 0; i < nr_objects; i++) { + struct object_entry *obj = &objects[i]; + + if (is_delta_type(obj->type)) + chain_histogram[obj->delta_depth - 1]++; + if (stat_only) + continue; + printf("%s %-6s %lu %lu %"PRIuMAX, + sha1_to_hex(obj->idx.sha1), + typename(obj->real_type), obj->size, + (unsigned long)(obj[1].idx.offset - obj->idx.offset), + (uintmax_t)obj->idx.offset); + if (is_delta_type(obj->type)) { + struct object_entry *bobj = &objects[obj->base_object_no]; + printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1)); + } + putchar('\n'); + } + + if (baseobjects) + printf("non delta: %d object%s\n", + baseobjects, baseobjects > 1 ? "s" : ""); + for (i = 0; i < deepest_delta; i++) { + if (!chain_histogram[i]) + continue; + printf("chain length = %d: %lu object%s\n", + i + 1, + chain_histogram[i], + chain_histogram[i] > 1 ? "s" : ""); + } +} + int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0; const char *curr_pack, *curr_index; const char *index_name = NULL, *pack_name = NULL; const char *keep_name = NULL, *keep_msg = NULL; char *index_name_buf = NULL, *keep_name_buf = NULL; struct pack_idx_entry **idx_objects; + struct pack_idx_option opts; unsigned char pack_sha1[20]; if (argc == 2 && !strcmp(argv[1], "-h")) @@ -884,7 +1028,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) read_replace_refs = 0; - git_config(git_index_pack_config, NULL); + reset_pack_idx_option(&opts); + git_config(git_index_pack_config, &opts); if (prefix && chdir(prefix)) die("Cannot come back to cwd"); @@ -898,6 +1043,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) fix_thin_pack = 1; } else if (!strcmp(arg, "--strict")) { strict = 1; + } else if (!strcmp(arg, "--verify")) { + verify = 1; + } else if (!strcmp(arg, "--verify-stat")) { + verify = 1; + stat = 1; + } else if (!strcmp(arg, "--verify-stat-only")) { + verify = 1; + stat = 1; + stat_only = 1; } else if (!strcmp(arg, "--keep")) { keep_msg = ""; } else if (!prefixcmp(arg, "--keep=")) { @@ -923,12 +1077,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) index_name = argv[++i]; } else if (!prefixcmp(arg, "--index-version=")) { char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) + opts.version = strtoul(arg + 16, &c, 10); + if (opts.version > 2) die("bad %s", arg); if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) + opts.off32_limit = strtoul(c+1, &c, 0); + if (*c || opts.off32_limit & 0x80000000) die("bad %s", arg); } else usage(index_pack_usage); @@ -964,11 +1118,17 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) strcpy(keep_name_buf + len - 5, ".keep"); keep_name = keep_name_buf; } + if (verify) { + if (!index_name) + die("--verify with no packfile name given"); + read_idx_option(&opts, index_name); + opts.flags |= WRITE_IDX_VERIFY; + } curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry)); - deltas = xmalloc(nr_objects * sizeof(struct delta_entry)); + objects = xcalloc(nr_objects + 1, sizeof(struct object_entry)); + deltas = xcalloc(nr_objects, sizeof(struct delta_entry)); parse_pack_objects(pack_sha1); if (nr_deltas == nr_resolved_deltas) { stop_progress(&progress); @@ -1008,16 +1168,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (strict) check_objects(); + if (stat) + show_pack_info(stat_only); + idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *)); for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; - curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1); + curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1); free(idx_objects); - final(pack_name, curr_pack, - index_name, curr_index, - keep_name, keep_msg, - pack_sha1); + if (!verify) + final(pack_name, curr_pack, + index_name, curr_index, + keep_name, keep_msg, + pack_sha1); + else + close(input_fd); free(objects); free(index_name_buf); free(keep_name_buf); diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index f08c5b0c9..6b666e1e8 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -173,7 +173,5 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) tree = parse_tree_indirect(sha1); if (!tree) die("not a tree object"); - read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); - - return 0; + return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index f402a843b..27f24d3aa 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -51,6 +51,8 @@ struct object_entry { * objects against. */ unsigned char no_try_delta; + unsigned char tagged; /* near the very tip of refs */ + unsigned char filled; /* assigned write-order */ }; /* @@ -70,6 +72,7 @@ static int local; static int incremental; static int ignore_packed_keep; static int allow_ofs_delta; +static struct pack_idx_option pack_idx_opts; static const char *base_name; static int progress = 1; static int window = 10; @@ -95,6 +98,7 @@ static unsigned long window_memory_limit = 0; */ static int *object_ix; static int object_ix_hashsz; +static struct object_entry *locate_object_entry(const unsigned char *sha1); /* * stats @@ -126,13 +130,13 @@ static void *get_delta(struct object_entry *entry) static unsigned long do_compress(void **pptr, unsigned long size) { - z_stream stream; + git_zstream stream; void *in, *out; unsigned long maxsize; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, pack_compression_level); - maxsize = deflateBound(&stream, size); + git_deflate_init(&stream, pack_compression_level); + maxsize = git_deflate_bound(&stream, size); in = *pptr; out = xmalloc(maxsize); @@ -142,9 +146,9 @@ static unsigned long do_compress(void **pptr, unsigned long size) stream.avail_in = size; stream.next_out = out; stream.avail_out = maxsize; - while (deflate(&stream, Z_FINISH) == Z_OK) + while (git_deflate(&stream, Z_FINISH) == Z_OK) ; /* nothing */ - deflateEnd(&stream); + git_deflate_end(&stream); free(in); return stream.total_out; @@ -160,7 +164,7 @@ static int check_pack_inflate(struct packed_git *p, off_t len, unsigned long expect) { - z_stream stream; + git_zstream stream; unsigned char fakebuf[4096], *in; int st; @@ -187,18 +191,19 @@ static void copy_pack_data(struct sha1file *f, off_t len) { unsigned char *in; - unsigned int avail; + unsigned long avail; while (len) { in = use_pack(p, w_curs, offset, &avail); if (avail > len) - avail = (unsigned int)len; + avail = (unsigned long)len; sha1write(f, in, avail); offset += avail; len -= avail; } } +/* Return 0 if we will bust the pack-size limit */ static unsigned long write_object(struct sha1file *f, struct object_entry *entry, off_t write_offset) @@ -433,6 +438,134 @@ static int write_one(struct sha1file *f, return 1; } +static int mark_tagged(const char *path, const unsigned char *sha1, int flag, + void *cb_data) +{ + unsigned char peeled[20]; + struct object_entry *entry = locate_object_entry(sha1); + + if (entry) + entry->tagged = 1; + if (!peel_ref(path, peeled)) { + entry = locate_object_entry(peeled); + if (entry) + entry->tagged = 1; + } + return 0; +} + +static void add_to_write_order(struct object_entry **wo, + int *endp, + struct object_entry *e) +{ + if (e->filled) + return; + wo[(*endp)++] = e; + e->filled = 1; +} + +static void add_descendants_to_write_order(struct object_entry **wo, + int *endp, + struct object_entry *e) +{ + struct object_entry *child; + + for (child = e->delta_child; child; child = child->delta_sibling) + add_to_write_order(wo, endp, child); + for (child = e->delta_child; child; child = child->delta_sibling) + add_descendants_to_write_order(wo, endp, child); +} + +static void add_family_to_write_order(struct object_entry **wo, + int *endp, + struct object_entry *e) +{ + struct object_entry *root; + + for (root = e; root->delta; root = root->delta) + ; /* nothing */ + add_to_write_order(wo, endp, root); + add_descendants_to_write_order(wo, endp, root); +} + +static struct object_entry **compute_write_order(void) +{ + int i, wo_end; + + struct object_entry **wo = xmalloc(nr_objects * sizeof(*wo)); + + for (i = 0; i < nr_objects; i++) { + objects[i].tagged = 0; + objects[i].filled = 0; + objects[i].delta_child = NULL; + objects[i].delta_sibling = NULL; + } + + /* + * Fully connect delta_child/delta_sibling network. + * Make sure delta_sibling is sorted in the original + * recency order. + */ + for (i = nr_objects - 1; 0 <= i; i--) { + struct object_entry *e = &objects[i]; + if (!e->delta) + continue; + /* Mark me as the first child */ + e->delta_sibling = e->delta->delta_child; + e->delta->delta_child = e; + } + + /* + * Mark objects that are at the tip of tags. + */ + for_each_tag_ref(mark_tagged, NULL); + + /* + * Give the commits in the original recency order until + * we see a tagged tip. + */ + for (i = wo_end = 0; i < nr_objects; i++) { + if (objects[i].tagged) + break; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * Then fill all the tagged tips. + */ + for (; i < nr_objects; i++) { + if (objects[i].tagged) + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * And then all remaining commits and tags. + */ + for (i = 0; i < nr_objects; i++) { + if (objects[i].type != OBJ_COMMIT && + objects[i].type != OBJ_TAG) + continue; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * And then all the trees. + */ + for (i = 0; i < nr_objects; i++) { + if (objects[i].type != OBJ_TREE) + continue; + add_to_write_order(wo, &wo_end, &objects[i]); + } + + /* + * Finally all the rest in really tight order + */ + for (i = 0; i < nr_objects; i++) + add_family_to_write_order(wo, &wo_end, &objects[i]); + + return wo; +} + static void write_pack_file(void) { uint32_t i = 0, j; @@ -441,10 +574,12 @@ static void write_pack_file(void) struct pack_header hdr; uint32_t nr_remaining = nr_result; time_t last_mtime = 0; + struct object_entry **write_order; if (progress > pack_to_stdout) progress_state = start_progress("Writing objects", nr_result); written_list = xmalloc(nr_objects * sizeof(*written_list)); + write_order = compute_write_order(); do { unsigned char sha1[20]; @@ -468,7 +603,8 @@ static void write_pack_file(void) offset = sizeof(hdr); nr_written = 0; for (; i < nr_objects; i++) { - if (!write_one(f, objects + i, &offset)) + struct object_entry *e = write_order[i]; + if (!write_one(f, e, &offset)) break; display_progress(progress_state, written); } @@ -493,8 +629,8 @@ static void write_pack_file(void) const char *idx_tmp_name; char tmpname[PATH_MAX]; - idx_tmp_name = write_idx_file(NULL, written_list, - nr_written, sha1); + idx_tmp_name = write_idx_file(NULL, written_list, nr_written, + &pack_idx_opts, sha1); snprintf(tmpname, sizeof(tmpname), "%s-%s.pack", base_name, sha1_to_hex(sha1)); @@ -545,6 +681,7 @@ static void write_pack_file(void) } while (nr_remaining && i < nr_objects); free(written_list); + free(write_order); stop_progress(&progress_state); if (written != nr_result) die("wrote %"PRIu32" objects while expecting %"PRIu32, @@ -994,7 +1131,7 @@ static void check_object(struct object_entry *entry) const unsigned char *base_ref = NULL; struct object_entry *base_entry; unsigned long used, used_0; - unsigned int avail; + unsigned long avail; off_t ofs; unsigned char *buf, c; @@ -1884,10 +2021,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) + pack_idx_opts.version = git_config_int(k, v); + if (pack_idx_opts.version > 2) die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); + pack_idx_opts.version); return 0; } if (!strcmp(k, "pack.packsizelimit")) { @@ -2134,6 +2271,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) rp_av[1] = "--objects"; /* --thin will make it --objects-edge */ rp_ac = 2; + reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) pack_compression_level = core_compression_level; @@ -2278,12 +2416,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } if (!prefixcmp(arg, "--index-version=")) { char *c; - pack_idx_default_version = strtoul(arg + 16, &c, 10); - if (pack_idx_default_version > 2) + pack_idx_opts.version = strtoul(arg + 16, &c, 10); + if (pack_idx_opts.version > 2) die("bad %s", arg); if (*c == ',') - pack_idx_off32_limit = strtoul(c+1, &c, 0); - if (*c || pack_idx_off32_limit & 0x80000000) + pack_idx_opts.off32_limit = strtoul(c+1, &c, 0); + if (*c || pack_idx_opts.off32_limit & 0x80000000) die("bad %s", arg); continue; } diff --git a/builtin/remote.c b/builtin/remote.c index 9ff1cac69..05b1f5b76 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -88,16 +88,6 @@ static inline int postfixcmp(const char *string, const char *postfix) return strcmp(string + len1 - len2, postfix); } -static int opt_parse_track(const struct option *opt, const char *arg, int not) -{ - struct string_list *list = opt->value; - if (not) - string_list_clear(list, 0); - else - string_list_append(list, arg); - return 0; -} - static int fetch_remote(const char *name) { const char *argv[] = { "fetch", name, NULL, NULL }; @@ -176,8 +166,8 @@ static int add(int argc, const char **argv) TAGS_SET), OPT_SET_INT(0, NULL, &fetch_tags, "or do not fetch any tag at all (--no-tags)", TAGS_UNSET), - OPT_CALLBACK('t', "track", &track, "branch", - "branch(es) to track", opt_parse_track), + OPT_STRING_LIST('t', "track", &track, "branch", + "branch(es) to track"), OPT_STRING('m', "master", &master, "branch", "master branch"), { OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch", "set up remote as a mirror to push to or fetch from", diff --git a/builtin/reset.c b/builtin/reset.c index 98bca044c..811e8e252 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -33,25 +33,6 @@ static const char *reset_type_names[] = { N_("mixed"), N_("soft"), N_("hard"), N_("merge"), N_("keep"), NULL }; -static char *args_to_str(const char **argv) -{ - char *buf = NULL; - unsigned long len, space = 0, nr = 0; - - for (; *argv; argv++) { - len = strlen(*argv); - ALLOC_GROW(buf, nr + 1 + len, space); - if (nr) - buf[nr++] = ' '; - memcpy(buf + nr, *argv, len); - nr += len; - } - ALLOC_GROW(buf, nr + 1, space); - buf[nr] = '\0'; - - return buf; -} - static inline int is_merge(void) { return !access(git_path("MERGE_HEAD"), F_OK); @@ -162,7 +143,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, for (i = 0; i < q->nr; i++) { struct diff_filespec *one = q->queue[i]->one; - if (one->mode) { + if (one->mode && !is_null_sha1(one->sha1)) { struct cache_entry *ce; ce = make_cache_entry(one->mode, one->sha1, one->path, 0, 0); @@ -215,14 +196,18 @@ static int read_from_tree(const char *prefix, const char **argv, return update_index_refresh(index_fd, lock, refresh_flags); } -static void prepend_reflog_action(const char *action, char *buf, size_t size) +static void set_reflog_message(struct strbuf *sb, const char *action, + const char *rev) { - const char *sep = ": "; const char *rla = getenv("GIT_REFLOG_ACTION"); - if (!rla) - rla = sep = ""; - if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size) - warning(_("Reflog action message too long: %.*s..."), 50, buf); + + strbuf_reset(sb); + if (rla) + strbuf_addf(sb, "%s: %s", rla, action); + else if (rev) + strbuf_addf(sb, "reset: moving to %s", rev); + else + strbuf_addf(sb, "reset: %s", action); } static void die_if_unmerged_cache(int reset_type) @@ -241,7 +226,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; struct commit *commit; - char *reflog_action, msg[1024]; + struct strbuf msg = STRBUF_INIT; const struct option options[] = { OPT__QUIET(&quiet, "be quiet, only report errors"), OPT_SET_INT(0, "mixed", &reset_type, @@ -261,8 +246,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); - reflog_action = args_to_str(argv); - setenv("GIT_REFLOG_ACTION", reflog_action, 0); /* * Possible arguments are: @@ -357,13 +340,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) old_orig = sha1_old_orig; if (!get_sha1("HEAD", sha1_orig)) { orig = sha1_orig; - prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg)); - update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); + set_reflog_message(&msg, "updating ORIG_HEAD", NULL); + update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR); } else if (old_orig) delete_ref("ORIG_HEAD", old_orig, 0); - prepend_reflog_action("updating HEAD", msg, sizeof(msg)); - update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR); + set_reflog_message(&msg, "updating HEAD", rev); + update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR); switch (reset_type) { case HARD: @@ -380,7 +363,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) remove_branch_state(); - free(reflog_action); + strbuf_release(&msg); return update_ref_status; } diff --git a/builtin/tag.c b/builtin/tag.c index ec926fc8e..667515e52 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -12,11 +12,13 @@ #include "tag.h" #include "run-command.h" #include "parse-options.h" +#include "diff.h" +#include "revision.h" static const char * const git_tag_usage[] = { "git tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]", "git tag -d <tagname>...", - "git tag -l [-n[<num>]] [<pattern>]", + "git tag -l [-n[<num>]] [<pattern>...]", "git tag -v <tagname>...", NULL }; @@ -24,17 +26,70 @@ static const char * const git_tag_usage[] = { static char signingkey[1000]; struct tag_filter { - const char *pattern; + const char **patterns; int lines; struct commit_list *with_commit; }; +static int match_pattern(const char **patterns, const char *ref) +{ + /* no pattern means match everything */ + if (!*patterns) + return 1; + for (; *patterns; patterns++) + if (!fnmatch(*patterns, ref, 0)) + return 1; + return 0; +} + +static int in_commit_list(const struct commit_list *want, struct commit *c) +{ + for (; want; want = want->next) + if (!hashcmp(want->item->object.sha1, c->object.sha1)) + return 1; + return 0; +} + +static int contains_recurse(struct commit *candidate, + const struct commit_list *want) +{ + struct commit_list *p; + + /* was it previously marked as containing a want commit? */ + if (candidate->object.flags & TMP_MARK) + return 1; + /* or marked as not possibly containing a want commit? */ + if (candidate->object.flags & UNINTERESTING) + return 0; + /* or are we it? */ + if (in_commit_list(want, candidate)) + return 1; + + if (parse_commit(candidate) < 0) + return 0; + + /* Otherwise recurse and mark ourselves for future traversals. */ + for (p = candidate->parents; p; p = p->next) { + if (contains_recurse(p->item, want)) { + candidate->object.flags |= TMP_MARK; + return 1; + } + } + candidate->object.flags |= UNINTERESTING; + return 0; +} + +static int contains(struct commit *candidate, const struct commit_list *want) +{ + return contains_recurse(candidate, want); +} + static int show_reference(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { struct tag_filter *filter = cb_data; - if (!fnmatch(filter->pattern, refname, 0)) { + if (match_pattern(filter->patterns, refname)) { int i; unsigned long size; enum object_type type; @@ -47,7 +102,7 @@ static int show_reference(const char *refname, const unsigned char *sha1, commit = lookup_commit_reference_gently(sha1, 1); if (!commit) return 0; - if (!is_descendant_of(commit, filter->with_commit)) + if (!contains(commit, filter->with_commit)) return 0; } @@ -88,15 +143,12 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } -static int list_tags(const char *pattern, int lines, +static int list_tags(const char **patterns, int lines, struct commit_list *with_commit) { struct tag_filter filter; - if (pattern == NULL) - pattern = "*"; - - filter.pattern = pattern; + filter.patterns = patterns; filter.lines = lines; filter.with_commit = with_commit; @@ -425,7 +477,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (list + delete + verify > 1) usage_with_options(git_tag_usage, options); if (list) - return list_tags(argv[0], lines == -1 ? 0 : lines, + return list_tags(argv, lines == -1 ? 0 : lines, with_commit); if (lines != -1) die(_("-n option is only allowed with -l.")); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index f63973c91..14e04e679 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -90,7 +90,7 @@ static void use(int bytes) static void *get_data(unsigned long size) { - z_stream stream; + git_zstream stream; void *buf = xmalloc(size); memset(&stream, 0, sizeof(stream)); diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 73f788ef2..2d0b38333 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -64,7 +64,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix) sent_argv[sent_argc] = NULL; /* parse all options sent by the client */ - return write_archive(sent_argc, sent_argv, prefix, 0); + return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1); } __attribute__((format (printf, 1, 2))) diff --git a/builtin/verify-pack.c b/builtin/verify-pack.c index b6079ae6c..e841b4a38 100644 --- a/builtin/verify-pack.c +++ b/builtin/verify-pack.c @@ -1,134 +1,53 @@ #include "builtin.h" #include "cache.h" -#include "pack.h" -#include "pack-revindex.h" +#include "run-command.h" #include "parse-options.h" -#define MAX_CHAIN 50 - #define VERIFY_PACK_VERBOSE 01 #define VERIFY_PACK_STAT_ONLY 02 -static void show_pack_info(struct packed_git *p, unsigned int flags) -{ - uint32_t nr_objects, i; - int cnt; - int stat_only = flags & VERIFY_PACK_STAT_ONLY; - unsigned long chain_histogram[MAX_CHAIN+1], baseobjects; - - nr_objects = p->num_objects; - memset(chain_histogram, 0, sizeof(chain_histogram)); - baseobjects = 0; - - for (i = 0; i < nr_objects; i++) { - const unsigned char *sha1; - unsigned char base_sha1[20]; - const char *type; - unsigned long size; - unsigned long store_size; - off_t offset; - unsigned int delta_chain_length; - - sha1 = nth_packed_object_sha1(p, i); - if (!sha1) - die("internal error pack-check nth-packed-object"); - offset = nth_packed_object_offset(p, i); - type = packed_object_info_detail(p, offset, &size, &store_size, - &delta_chain_length, - base_sha1); - if (!stat_only) - printf("%s ", sha1_to_hex(sha1)); - if (!delta_chain_length) { - if (!stat_only) - printf("%-6s %lu %lu %"PRIuMAX"\n", - type, size, store_size, (uintmax_t)offset); - baseobjects++; - } - else { - if (!stat_only) - printf("%-6s %lu %lu %"PRIuMAX" %u %s\n", - type, size, store_size, (uintmax_t)offset, - delta_chain_length, sha1_to_hex(base_sha1)); - if (delta_chain_length <= MAX_CHAIN) - chain_histogram[delta_chain_length]++; - else - chain_histogram[0]++; - } - } - - if (baseobjects) - printf("non delta: %lu object%s\n", - baseobjects, baseobjects > 1 ? "s" : ""); - - for (cnt = 1; cnt <= MAX_CHAIN; cnt++) { - if (!chain_histogram[cnt]) - continue; - printf("chain length = %d: %lu object%s\n", cnt, - chain_histogram[cnt], - chain_histogram[cnt] > 1 ? "s" : ""); - } - if (chain_histogram[0]) - printf("chain length > %d: %lu object%s\n", MAX_CHAIN, - chain_histogram[0], - chain_histogram[0] > 1 ? "s" : ""); -} - static int verify_one_pack(const char *path, unsigned int flags) { - char arg[PATH_MAX]; - int len; + struct child_process index_pack; + const char *argv[] = {"index-pack", NULL, NULL, NULL }; + struct strbuf arg = STRBUF_INIT; int verbose = flags & VERIFY_PACK_VERBOSE; int stat_only = flags & VERIFY_PACK_STAT_ONLY; - struct packed_git *pack; int err; - len = strlcpy(arg, path, PATH_MAX); - if (len >= PATH_MAX) - return error("name too long: %s", path); - - /* - * In addition to "foo.idx" we accept "foo.pack" and "foo"; - * normalize these forms to "foo.idx" for add_packed_git(). - */ - if (has_extension(arg, ".pack")) { - strcpy(arg + len - 5, ".idx"); - len--; - } else if (!has_extension(arg, ".idx")) { - if (len + 4 >= PATH_MAX) - return error("name too long: %s.idx", arg); - strcpy(arg + len, ".idx"); - len += 4; - } + if (stat_only) + argv[1] = "--verify-stat-only"; + else if (verbose) + argv[1] = "--verify-stat"; + else + argv[1] = "--verify"; /* - * add_packed_git() uses our buffer (containing "foo.idx") to - * build the pack filename ("foo.pack"). Make sure it fits. + * In addition to "foo.pack" we accept "foo.idx" and "foo"; + * normalize these forms to "foo.pack" for "index-pack --verify". */ - if (len + 1 >= PATH_MAX) { - arg[len - 4] = '\0'; - return error("name too long: %s.pack", arg); - } - - pack = add_packed_git(arg, len, 1); - if (!pack) - return error("packfile %s not found.", arg); + strbuf_addstr(&arg, path); + if (has_extension(arg.buf, ".idx")) + strbuf_splice(&arg, arg.len - 3, 3, "pack", 4); + else if (!has_extension(arg.buf, ".pack")) + strbuf_add(&arg, ".pack", 5); + argv[2] = arg.buf; - install_packed_git(pack); + memset(&index_pack, 0, sizeof(index_pack)); + index_pack.argv = argv; + index_pack.git_cmd = 1; - if (!stat_only) - err = verify_pack(pack); - else - err = open_pack_index(pack); + err = run_command(&index_pack); if (verbose || stat_only) { if (err) - printf("%s: bad\n", pack->pack_name); + printf("%s: bad\n", arg.buf); else { - show_pack_info(pack, flags); if (!stat_only) - printf("%s: ok\n", pack->pack_name); + printf("%s: ok\n", arg.buf); } } + strbuf_release(&arg); return err; } @@ -159,7 +78,6 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) for (i = 0; i < argc; i++) { if (verify_one_pack(argv[i], flags)) err = 1; - discard_revindex(); } return err; @@ -6,6 +6,7 @@ #include "hash.h" #include "advice.h" #include "gettext.h" +#include "convert.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@ -16,13 +17,27 @@ #endif #include <zlib.h> -#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 -#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) -#endif - -void git_inflate_init(z_streamp strm); -void git_inflate_end(z_streamp strm); -int git_inflate(z_streamp strm, int flush); +typedef struct git_zstream { + z_stream z; + unsigned long avail_in; + unsigned long avail_out; + unsigned long total_in; + unsigned long total_out; + unsigned char *next_in; + unsigned char *next_out; +} git_zstream; + +void git_inflate_init(git_zstream *); +void git_inflate_init_gzip_only(git_zstream *); +void git_inflate_end(git_zstream *); +int git_inflate(git_zstream *, int flush); + +void git_deflate_init(git_zstream *, int level); +void git_deflate_init_gzip(git_zstream *, int level); +void git_deflate_end(git_zstream *); +int git_deflate_end_gently(git_zstream *); +int git_deflate(git_zstream *, int flush); +unsigned long git_deflate_bound(git_zstream *, unsigned long); #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT) #define DTYPE(de) ((de)->d_type) @@ -582,35 +597,6 @@ extern int fsync_object_files; extern int core_preload_index; extern int core_apply_sparse_checkout; -enum safe_crlf { - SAFE_CRLF_FALSE = 0, - SAFE_CRLF_FAIL = 1, - SAFE_CRLF_WARN = 2 -}; - -extern enum safe_crlf safe_crlf; - -enum auto_crlf { - AUTO_CRLF_FALSE = 0, - AUTO_CRLF_TRUE = 1, - AUTO_CRLF_INPUT = -1 -}; - -extern enum auto_crlf auto_crlf; - -enum eol { - EOL_UNSET, - EOL_CRLF, - EOL_LF, -#ifdef NATIVE_CRLF - EOL_NATIVE = EOL_CRLF -#else - EOL_NATIVE = EOL_LF -#endif -}; - -extern enum eol core_eol; - enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_NEVER = 0, @@ -747,7 +733,7 @@ extern char *expand_user_path(const char *path); char *enter_repo(char *path, int strict); static inline int is_absolute_path(const char *path) { - return path[0] == '/' || has_dos_drive_prefix(path); + return is_dir_sep(path[0]) || has_dos_drive_prefix(path); } int is_directory(const char *); const char *real_path(const char *path); @@ -780,10 +766,16 @@ extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1); extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); +extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size); +extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz); +extern int parse_sha1_header(const char *hdr, unsigned long *sizep); /* global flag to enable extra checks when accessing packed objects */ extern int do_check_packed_object_crc; +/* for development: log offset of pack access */ +extern const char *log_pack_access; + extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); extern int move_temp_to_file(const char *tmpfile, const char *filename); @@ -1009,7 +1001,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); extern int open_pack_index(struct packed_git *); extern void close_pack_index(struct packed_git *); -extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); +extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *); extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); extern void free_pack_by_name(const char *); @@ -1021,7 +1013,36 @@ extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); -extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); +extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *); + +struct object_info { + /* Request */ + unsigned long *sizep; + + /* Response */ + enum { + OI_CACHED, + OI_LOOSE, + OI_PACKED, + OI_DBCACHED + } whence; + union { + /* + * struct { + * ... Nothing to expose in this case + * } cached; + * struct { + * ... Nothing to expose in this case + * } loose; + */ + struct { + struct packed_git *pack; + off_t offset; + unsigned int is_delta; + } packed; + } u; +}; +extern int sha1_object_info_extended(const unsigned char *, struct object_info *); /* Dumb servers support */ extern int update_server_info(int); @@ -1063,6 +1084,8 @@ extern int config_error_nonbool(const char *); extern const char *get_log_output_encoding(void); extern const char *get_commit_output_encoding(void); +extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data); + extern const char *config_exclusive_filename; #define MAX_GITNAME (1000) @@ -1129,13 +1152,6 @@ extern void trace_strbuf(const char *key, const struct strbuf *buf); void packet_trace_identity(const char *prog); -/* convert.c */ -/* returns 1 if *dst was used */ -extern int convert_to_git(const char *path, const char *src, size_t len, - struct strbuf *dst, enum safe_crlf checksafe); -extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst); -extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst); - /* add */ /* * return 0 if success, 1 - if addition of a file failed and diff --git a/combine-diff.c b/combine-diff.c index 655fa89d8..be67cfcd4 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -7,6 +7,7 @@ #include "xdiff-interface.h" #include "log-tree.h" #include "refs.h" +#include "userdiff.h" static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { @@ -92,7 +93,9 @@ struct sline { unsigned long *p_lno; }; -static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned long *size) +static char *grab_blob(const unsigned char *sha1, unsigned int mode, + unsigned long *size, struct userdiff_driver *textconv, + const char *path) { char *blob; enum object_type type; @@ -105,6 +108,11 @@ static char *grab_blob(const unsigned char *sha1, unsigned int mode, unsigned lo /* deleted blob */ *size = 0; return xcalloc(1, 1); + } else if (textconv) { + struct diff_filespec *df = alloc_filespec(path); + fill_filespec(df, sha1, mode); + *size = fill_textconv(textconv, df, &blob); + free_filespec(df); } else { blob = read_sha1_file(sha1, &type, size); if (type != OBJ_BLOB) @@ -204,7 +212,9 @@ static void consume_line(void *state_, char *line, unsigned long len) static void combine_diff(const unsigned char *parent, unsigned int mode, mmfile_t *result_file, struct sline *sline, unsigned int cnt, int n, - int num_parent, int result_deleted) + int num_parent, int result_deleted, + struct userdiff_driver *textconv, + const char *path) { unsigned int p_lno, lno; unsigned long nmask = (1UL << n); @@ -217,7 +227,7 @@ static void combine_diff(const unsigned char *parent, unsigned int mode, if (result_deleted) return; /* result deleted */ - parent_file.ptr = grab_blob(parent, mode, &sz); + parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path); parent_file.size = sz; memset(&xpp, 0, sizeof(xpp)); xpp.flags = 0; @@ -681,6 +691,82 @@ static void dump_quoted_path(const char *head, puts(buf.buf); } +static void show_combined_header(struct combine_diff_path *elem, + int num_parent, + int dense, + struct rev_info *rev, + int mode_differs, + int show_file_header) +{ + struct diff_options *opt = &rev->diffopt; + int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV; + const char *a_prefix = opt->a_prefix ? opt->a_prefix : "a/"; + const char *b_prefix = opt->b_prefix ? opt->b_prefix : "b/"; + int use_color = DIFF_OPT_TST(opt, COLOR_DIFF); + const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); + const char *c_reset = diff_get_color(use_color, DIFF_RESET); + const char *abb; + int added = 0; + int deleted = 0; + int i; + + if (rev->loginfo && !rev->no_commit_id) + show_log(rev); + + dump_quoted_path(dense ? "diff --cc " : "diff --combined ", + "", elem->path, c_meta, c_reset); + printf("%sindex ", c_meta); + for (i = 0; i < num_parent; i++) { + abb = find_unique_abbrev(elem->parent[i].sha1, + abbrev); + printf("%s%s", i ? "," : "", abb); + } + abb = find_unique_abbrev(elem->sha1, abbrev); + printf("..%s%s\n", abb, c_reset); + + if (mode_differs) { + deleted = !elem->mode; + + /* We say it was added if nobody had it */ + added = !deleted; + for (i = 0; added && i < num_parent; i++) + if (elem->parent[i].status != + DIFF_STATUS_ADDED) + added = 0; + if (added) + printf("%snew file mode %06o", + c_meta, elem->mode); + else { + if (deleted) + printf("%sdeleted file ", c_meta); + printf("mode "); + for (i = 0; i < num_parent; i++) { + printf("%s%06o", i ? "," : "", + elem->parent[i].mode); + } + if (elem->mode) + printf("..%06o", elem->mode); + } + printf("%s\n", c_reset); + } + + if (!show_file_header) + return; + + if (added) + dump_quoted_path("--- ", "", "/dev/null", + c_meta, c_reset); + else + dump_quoted_path("--- ", a_prefix, elem->path, + c_meta, c_reset); + if (deleted) + dump_quoted_path("+++ ", "", "/dev/null", + c_meta, c_reset); + else + dump_quoted_path("+++ ", b_prefix, elem->path, + c_meta, c_reset); +} + static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int dense, struct rev_info *rev) { @@ -692,17 +778,22 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, int mode_differs = 0; int i, show_hunks; int working_tree_file = is_null_sha1(elem->sha1); - int abbrev = DIFF_OPT_TST(opt, FULL_INDEX) ? 40 : DEFAULT_ABBREV; - const char *a_prefix, *b_prefix; mmfile_t result_file; + struct userdiff_driver *userdiff; + struct userdiff_driver *textconv = NULL; + int is_binary; context = opt->context; - a_prefix = opt->a_prefix ? opt->a_prefix : "a/"; - b_prefix = opt->b_prefix ? opt->b_prefix : "b/"; + userdiff = userdiff_find_by_path(elem->path); + if (!userdiff) + userdiff = userdiff_find_by_name("default"); + if (DIFF_OPT_TST(opt, ALLOW_TEXTCONV)) + textconv = userdiff_get_textconv(userdiff); /* Read the result of merge first */ if (!working_tree_file) - result = grab_blob(elem->sha1, elem->mode, &result_size); + result = grab_blob(elem->sha1, elem->mode, &result_size, + textconv, elem->path); else { /* Used by diff-tree to read from the working tree */ struct stat st; @@ -725,9 +816,16 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, } else if (S_ISDIR(st.st_mode)) { unsigned char sha1[20]; if (resolve_gitlink_ref(elem->path, "HEAD", sha1) < 0) - result = grab_blob(elem->sha1, elem->mode, &result_size); + result = grab_blob(elem->sha1, elem->mode, + &result_size, NULL, NULL); else - result = grab_blob(sha1, elem->mode, &result_size); + result = grab_blob(sha1, elem->mode, + &result_size, NULL, NULL); + } else if (textconv) { + struct diff_filespec *df = alloc_filespec(elem->path); + fill_filespec(df, null_sha1, st.st_mode); + result_size = fill_textconv(textconv, df, &result); + free_filespec(df); } else if (0 <= (fd = open(elem->path, O_RDONLY))) { size_t len = xsize_t(st.st_size); ssize_t done; @@ -777,6 +875,38 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, close(fd); } + for (i = 0; i < num_parent; i++) { + if (elem->parent[i].mode != elem->mode) { + mode_differs = 1; + break; + } + } + + if (textconv) + is_binary = 0; + else if (userdiff->binary != -1) + is_binary = userdiff->binary; + else { + is_binary = buffer_is_binary(result, result_size); + for (i = 0; !is_binary && i < num_parent; i++) { + char *buf; + unsigned long size; + buf = grab_blob(elem->parent[i].sha1, + elem->parent[i].mode, + &size, NULL, NULL); + if (buffer_is_binary(buf, size)) + is_binary = 1; + free(buf); + } + } + if (is_binary) { + show_combined_header(elem, num_parent, dense, rev, + mode_differs, 0); + printf("Binary files differ\n"); + free(result); + return; + } + for (cnt = 0, cp = result; cp < result + result_size; cp++) { if (*cp == '\n') cnt++; @@ -824,71 +954,15 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, combine_diff(elem->parent[i].sha1, elem->parent[i].mode, &result_file, sline, - cnt, i, num_parent, result_deleted); - if (elem->parent[i].mode != elem->mode) - mode_differs = 1; + cnt, i, num_parent, result_deleted, + textconv, elem->path); } show_hunks = make_hunks(sline, cnt, num_parent, dense); if (show_hunks || mode_differs || working_tree_file) { - const char *abb; - int use_color = DIFF_OPT_TST(opt, COLOR_DIFF); - const char *c_meta = diff_get_color(use_color, DIFF_METAINFO); - const char *c_reset = diff_get_color(use_color, DIFF_RESET); - int added = 0; - int deleted = 0; - - if (rev->loginfo && !rev->no_commit_id) - show_log(rev); - dump_quoted_path(dense ? "diff --cc " : "diff --combined ", - "", elem->path, c_meta, c_reset); - printf("%sindex ", c_meta); - for (i = 0; i < num_parent; i++) { - abb = find_unique_abbrev(elem->parent[i].sha1, - abbrev); - printf("%s%s", i ? "," : "", abb); - } - abb = find_unique_abbrev(elem->sha1, abbrev); - printf("..%s%s\n", abb, c_reset); - - if (mode_differs) { - deleted = !elem->mode; - - /* We say it was added if nobody had it */ - added = !deleted; - for (i = 0; added && i < num_parent; i++) - if (elem->parent[i].status != - DIFF_STATUS_ADDED) - added = 0; - if (added) - printf("%snew file mode %06o", - c_meta, elem->mode); - else { - if (deleted) - printf("%sdeleted file ", c_meta); - printf("mode "); - for (i = 0; i < num_parent; i++) { - printf("%s%06o", i ? "," : "", - elem->parent[i].mode); - } - if (elem->mode) - printf("..%06o", elem->mode); - } - printf("%s\n", c_reset); - } - if (added) - dump_quoted_path("--- ", "", "/dev/null", - c_meta, c_reset); - else - dump_quoted_path("--- ", a_prefix, elem->path, - c_meta, c_reset); - if (deleted) - dump_quoted_path("+++ ", "", "/dev/null", - c_meta, c_reset); - else - dump_quoted_path("+++ ", b_prefix, elem->path, - c_meta, c_reset); + show_combined_header(elem, num_parent, dense, rev, + mode_differs, 1); dump_sline(sline, cnt, num_parent, DIFF_OPT_TST(opt, COLOR_DIFF), result_deleted); } diff --git a/compat/cygwin.c b/compat/cygwin.c index b4a51b958..dfe9b3084 100644 --- a/compat/cygwin.c +++ b/compat/cygwin.c @@ -101,7 +101,7 @@ static int cygwin_stat(const char *path, struct stat *buf) * and calling git_default_config() from here would break such variables. */ static int native_stat = 1; -static int core_filemode; +static int core_filemode = 1; /* matches trust_executable_bit default */ static int git_cygwin_config(const char *var, const char *value, void *cb) { @@ -114,8 +114,7 @@ static int git_cygwin_config(const char *var, const char *value, void *cb) static int init_stat(void) { - if (have_git_dir()) { - git_config(git_cygwin_config, NULL); + if (have_git_dir() && git_config(git_cygwin_config,NULL)) { if (!core_filemode && native_stat) { cygwin_stat_fn = cygwin_stat; cygwin_lstat_fn = cygwin_lstat; diff --git a/compat/mingw.c b/compat/mingw.c index f6e9ff776..6ef0cc4f9 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -178,7 +178,7 @@ static int ask_yes_no_if_possible(const char *format, ...) vsnprintf(question, sizeof(question), format, args); va_end(args); - if ((retry_hook[0] = getenv("GIT_ASK_YESNO"))) { + if ((retry_hook[0] = mingw_getenv("GIT_ASK_YESNO"))) { retry_hook[1] = question; return !run_command_v_opt(retry_hook, 0); } @@ -599,19 +599,6 @@ char *mingw_getcwd(char *pointer, int len) return ret; } -#undef getenv -char *mingw_getenv(const char *name) -{ - char *result = getenv(name); - if (!result && !strcmp(name, "TMPDIR")) { - /* on Windows it is TMP and TEMP */ - result = getenv("TMP"); - if (!result) - result = getenv("TEMP"); - } - return result; -} - /* * See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx * (Parsing C++ Command-Line Arguments) @@ -711,7 +698,7 @@ static const char *parse_interpreter(const char *cmd) */ static char **get_path_split(void) { - char *p, **path, *envpath = getenv("PATH"); + char *p, **path, *envpath = mingw_getenv("PATH"); int i, n = 0; if (!envpath || !*envpath) @@ -1128,6 +1115,36 @@ char **make_augmented_environ(const char *const *vars) return env; } +#undef getenv + +/* + * The system's getenv looks up the name in a case-insensitive manner. + * This version tries a case-sensitive lookup and falls back to + * case-insensitive if nothing was found. This is necessary because, + * as a prominent example, CMD sets 'Path', but not 'PATH'. + * Warning: not thread-safe. + */ +static char *getenv_cs(const char *name) +{ + size_t len = strlen(name); + int i = lookup_env(environ, name, len); + if (i >= 0) + return environ[i] + len + 1; /* skip past name and '=' */ + return getenv(name); +} + +char *mingw_getenv(const char *name) +{ + char *result = getenv_cs(name); + if (!result && !strcmp(name, "TMPDIR")) { + /* on Windows it is TMP and TEMP */ + result = getenv_cs("TMP"); + if (!result) + result = getenv_cs("TEMP"); + } + return result; +} + /* * Note, this isn't a complete replacement for getaddrinfo. It assumes * that service contains a numerical port, or that it is null. It diff --git a/compat/mingw.h b/compat/mingw.h index 547568b91..ce9dd980e 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -300,6 +300,15 @@ int winansi_fprintf(FILE *stream, const char *format, ...) __attribute__((format #define has_dos_drive_prefix(path) (isalpha(*(path)) && (path)[1] == ':') #define is_dir_sep(c) ((c) == '/' || (c) == '\\') +static inline char *mingw_find_last_dir_sep(const char *path) +{ + char *ret = NULL; + for (; *path; ++path) + if (is_dir_sep(*path)) + ret = (char *)path; + return ret; +} +#define find_last_dir_sep mingw_find_last_dir_sep #define PATH_SEP ';' #define PRIuMAX "I64u" @@ -12,10 +12,18 @@ #define MAXNAME (256) -static FILE *config_file; -static const char *config_file_name; -static int config_linenr; -static int config_file_eof; +typedef struct config_file { + struct config_file *prev; + FILE *f; + const char *name; + int linenr; + int eof; + struct strbuf value; + char var[MAXNAME]; +} config_file; + +static config_file *cf; + static int zlib_compression_seen; const char *config_exclusive_filename = NULL; @@ -39,13 +47,13 @@ void git_config_push_parameter(const char *text) strbuf_release(&env); } -static int git_config_parse_parameter(const char *text, - config_fn_t fn, void *data) +int git_config_parse_parameter(const char *text, + config_fn_t fn, void *data) { - struct strbuf tmp = STRBUF_INIT; struct strbuf **pair; - strbuf_addstr(&tmp, text); - pair = strbuf_split(&tmp, '='); + pair = strbuf_split_str(text, '=', 2); + if (!pair[0]) + return error("bogus config parameter: %s", text); if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') strbuf_setlen(pair[0], pair[0]->len - 1); strbuf_trim(pair[0]); @@ -99,7 +107,7 @@ static int get_next_char(void) FILE *f; c = '\n'; - if ((f = config_file) != NULL) { + if (cf && ((f = cf->f) != NULL)) { c = fgetc(f); if (c == '\r') { /* DOS like systems */ @@ -110,9 +118,9 @@ static int get_next_char(void) } } if (c == '\n') - config_linenr++; + cf->linenr++; if (c == EOF) { - config_file_eof = 1; + cf->eof = 1; c = '\n'; } } @@ -121,21 +129,20 @@ static int get_next_char(void) static char *parse_value(void) { - static struct strbuf value = STRBUF_INIT; int quote = 0, comment = 0, space = 0; - strbuf_reset(&value); + strbuf_reset(&cf->value); for (;;) { int c = get_next_char(); if (c == '\n') { if (quote) return NULL; - return value.buf; + return cf->value.buf; } if (comment) continue; if (isspace(c) && !quote) { - if (value.len) + if (cf->value.len) space++; continue; } @@ -146,7 +153,7 @@ static char *parse_value(void) } } for (; space; space--) - strbuf_addch(&value, ' '); + strbuf_addch(&cf->value, ' '); if (c == '\\') { c = get_next_char(); switch (c) { @@ -168,14 +175,14 @@ static char *parse_value(void) default: return NULL; } - strbuf_addch(&value, c); + strbuf_addch(&cf->value, c); continue; } if (c == '"') { quote = 1-quote; continue; } - strbuf_addch(&value, c); + strbuf_addch(&cf->value, c); } } @@ -192,7 +199,7 @@ static int get_value(config_fn_t fn, void *data, char *name, unsigned int len) /* Get the full name */ for (;;) { c = get_next_char(); - if (config_file_eof) + if (cf->eof) break; if (!iskeychar(c)) break; @@ -256,7 +263,7 @@ static int get_base_var(char *name) for (;;) { int c = get_next_char(); - if (config_file_eof) + if (cf->eof) return -1; if (c == ']') return baselen; @@ -274,7 +281,7 @@ static int git_parse_file(config_fn_t fn, void *data) { int comment = 0; int baselen = 0; - static char var[MAXNAME]; + char *var = cf->var; /* U+FEFF Byte Order Mark in UTF8 */ static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf"; @@ -298,7 +305,7 @@ static int git_parse_file(config_fn_t fn, void *data) } } if (c == '\n') { - if (config_file_eof) + if (cf->eof) return 0; comment = 0; continue; @@ -323,7 +330,7 @@ static int git_parse_file(config_fn_t fn, void *data) if (get_value(fn, data, var, baselen+1) < 0) break; } - die("bad config file line %d in %s", config_linenr, config_file_name); + die("bad config file line %d in %s", cf->linenr, cf->name); } static int parse_unit_factor(const char *end, unsigned long *val) @@ -374,8 +381,8 @@ int git_parse_ulong(const char *value, unsigned long *ret) static void die_bad_config(const char *name) { - if (config_file_name) - die("bad config value for '%s' in %s", name, config_file_name); + if (cf && cf->name) + die("bad config value for '%s' in %s", name, cf->name); die("bad config value for '%s'", name); } @@ -569,6 +576,9 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.logpackaccess")) + return git_config_string(&log_pack_access, var, value); + if (!strcmp(var, "core.autocrlf")) { if (value && !strcasecmp(value, "input")) { if (core_eol == EOL_CRLF) @@ -795,13 +805,24 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data) ret = -1; if (f) { - config_file = f; - config_file_name = filename; - config_linenr = 1; - config_file_eof = 0; + config_file top; + + /* push config-file parsing state stack */ + top.prev = cf; + top.f = f; + top.name = filename; + top.linenr = 1; + top.eof = 0; + strbuf_init(&top.value, 1024); + cf = ⊤ + ret = git_parse_file(fn, data); + + /* pop config-file parsing state stack */ + strbuf_release(&top.value); + cf = top.prev; + fclose(f); - config_file_name = NULL; } return ret; } @@ -856,7 +877,7 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config) switch (git_config_from_parameters(fn, data)) { case -1: /* error */ - ret--; + die("unable to parse command-line config"); break; case 0: /* found nothing */ break; @@ -909,6 +930,7 @@ static int store_aux(const char *key, const char *value, void *cb) { const char *ep; size_t section_len; + FILE *f = cf->f; switch (store.state) { case KEY_SEEN: @@ -920,7 +942,7 @@ static int store_aux(const char *key, const char *value, void *cb) return 1; } - store.offset[store.seen] = ftell(config_file); + store.offset[store.seen] = ftell(f); store.seen++; } break; @@ -947,19 +969,19 @@ static int store_aux(const char *key, const char *value, void *cb) * Do not increment matches: this is no match, but we * just made sure we are in the desired section. */ - store.offset[store.seen] = ftell(config_file); + store.offset[store.seen] = ftell(f); /* fallthru */ case SECTION_END_SEEN: case START: if (matches(key, value)) { - store.offset[store.seen] = ftell(config_file); + store.offset[store.seen] = ftell(f); store.state = KEY_SEEN; store.seen++; } else { if (strrchr(key, '.') - key == store.baselen && !strncmp(key, store.key, store.baselen)) { store.state = SECTION_SEEN; - store.offset[store.seen] = ftell(config_file); + store.offset[store.seen] = ftell(f); } } } @@ -1415,6 +1437,7 @@ int git_config_rename_section(const char *old_name, const char *new_name) struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1); int out_fd; char buf[1024]; + FILE *config_file; if (config_exclusive_filename) config_filename = xstrdup(config_exclusive_filename); @@ -192,7 +192,8 @@ static const char *ai_name(const struct addrinfo *ai) */ static int git_tcp_connect_sock(char *host, int flags) { - int sockfd = -1, saved_errno = 0; + struct strbuf error_message = STRBUF_INIT; + int sockfd = -1; const char *port = STR(DEFAULT_GIT_PORT); struct addrinfo hints, *ai0, *ai; int gai; @@ -216,21 +217,15 @@ static int git_tcp_connect_sock(char *host, int flags) if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port); - for (ai0 = ai; ai; ai = ai->ai_next) { + for (ai0 = ai; ai; ai = ai->ai_next, cnt++) { sockfd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); - if (sockfd < 0) { - saved_errno = errno; - continue; - } - if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) { - saved_errno = errno; - fprintf(stderr, "%s[%d: %s]: errno=%s\n", - host, - cnt, - ai_name(ai), - strerror(saved_errno)); - close(sockfd); + if ((sockfd < 0) || + (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0)) { + strbuf_addf(&error_message, "%s[%d: %s]: errno=%s\n", + host, cnt, ai_name(ai), strerror(errno)); + if (0 <= sockfd) + close(sockfd); sockfd = -1; continue; } @@ -242,11 +237,13 @@ static int git_tcp_connect_sock(char *host, int flags) freeaddrinfo(ai0); if (sockfd < 0) - die("unable to connect a socket (%s)", strerror(saved_errno)); + die("unable to connect to %s:\n%s", host, error_message.buf); if (flags & CONNECT_VERBOSE) fprintf(stderr, "done.\n"); + strbuf_release(&error_message); + return sockfd; } diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt index 9718abf86..0565d83fc 100644 --- a/contrib/convert-objects/git-convert-objects.txt +++ b/contrib/convert-objects/git-convert-objects.txt @@ -8,6 +8,7 @@ git-convert-objects - Converts old-style git repository SYNOPSIS -------- +[verse] 'git-convert-objects' DESCRIPTION diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 98d2aee67..6b9de9e7e 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -1649,7 +1649,8 @@ class P4Sync(Command, P4UserMap): def importHeadRevision(self, revision): print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch) - details = { "user" : "git perforce import user", "time" : int(time.time()) } + details = {} + details["user"] = "git perforce import user" details["desc"] = ("Initial import of %s from the state at revision %s\n" % (' '.join(self.depotPaths), revision)) details["change"] = revision @@ -1689,6 +1690,18 @@ class P4Sync(Command, P4UserMap): fileCnt = fileCnt + 1 details["change"] = newestRevision + + # Use time from top-most change so that all git-p4 clones of + # the same p4 repo have the same commit SHA1s. + res = p4CmdList("describe -s %d" % newestRevision) + newestTime = None + for r in res: + if r.has_key('time'): + newestTime = int(r['time']) + if newestTime is None: + die("\"describe -s\" on newest change %d did not give a time") + details["time"] = newestTime + self.updateOptionDict(details) try: self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths) diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt index 77c29de30..9e12f9784 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -7,6 +7,7 @@ gitview - A GTK based repository browser for git SYNOPSIS -------- +[verse] 'gitview' [options] [args] DESCRIPTION diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 21989fc6a..fa6d41a1a 100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -60,6 +60,11 @@ # email body. If not specified, there is no limit. # Lines beyond the limit are suppressed and counted, and a final # line is added indicating the number of suppressed lines. +# hooks.diffopts +# Alternate options for the git diff-tree invocation that shows changes. +# Default is "--stat --summary --find-copies-harder". Add -p to those +# options to include a unified diff of changes in addition to the usual +# summary output. # # Notes # ----- @@ -446,7 +451,7 @@ generate_update_branch_email() # non-fast-forward updates. echo "" echo "Summary of changes:" - git diff-tree --stat --summary --find-copies-harder $oldrev..$newrev + git diff-tree $diffopts $oldrev..$newrev } # @@ -723,6 +728,8 @@ envelopesender=$(git config hooks.envelopesender) emailprefix=$(git config hooks.emailprefix || echo '[SCM] ') custom_showrev=$(git config hooks.showrev) maxlines=$(git config hooks.emailmaxlines) +diffopts=$(git config hooks.diffopts) +: ${diffopts:="--stat --summary --find-copies-harder"} # --- Main loop # Allow dual mode: run from the command line just like the update hook, or diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt index cd075b96c..72ffea0b3 100644 --- a/contrib/svn-fe/svn-fe.txt +++ b/contrib/svn-fe/svn-fe.txt @@ -7,6 +7,7 @@ svn-fe - convert an SVN "dumpfile" to a fast-import stream SYNOPSIS -------- +[verse] svnadmin dump --incremental REPO | svn-fe [url] | git fast-import DESCRIPTION @@ -813,3 +813,400 @@ int renormalize_buffer(const char *path, const char *src, size_t len, struct str } return ret | convert_to_git(path, src, len, dst, 0); } + +/***************************************************************** + * + * Streaming converison support + * + *****************************************************************/ + +typedef int (*filter_fn)(struct stream_filter *, + const char *input, size_t *isize_p, + char *output, size_t *osize_p); +typedef void (*free_fn)(struct stream_filter *); + +struct stream_filter_vtbl { + filter_fn filter; + free_fn free; +}; + +struct stream_filter { + struct stream_filter_vtbl *vtbl; +}; + +static int null_filter_fn(struct stream_filter *filter, + const char *input, size_t *isize_p, + char *output, size_t *osize_p) +{ + size_t count; + + if (!input) + return 0; /* we do not keep any states */ + count = *isize_p; + if (*osize_p < count) + count = *osize_p; + if (count) { + memmove(output, input, count); + *isize_p -= count; + *osize_p -= count; + } + return 0; +} + +static void null_free_fn(struct stream_filter *filter) +{ + ; /* nothing -- null instances are shared */ +} + +static struct stream_filter_vtbl null_vtbl = { + null_filter_fn, + null_free_fn, +}; + +static struct stream_filter null_filter_singleton = { + &null_vtbl, +}; + +int is_null_stream_filter(struct stream_filter *filter) +{ + return filter == &null_filter_singleton; +} + + +/* + * LF-to-CRLF filter + */ +static int lf_to_crlf_filter_fn(struct stream_filter *filter, + const char *input, size_t *isize_p, + char *output, size_t *osize_p) +{ + size_t count; + + if (!input) + return 0; /* we do not keep any states */ + count = *isize_p; + if (count) { + size_t i, o; + for (i = o = 0; o < *osize_p && i < count; i++) { + char ch = input[i]; + if (ch == '\n') { + if (o + 1 < *osize_p) + output[o++] = '\r'; + else + break; + } + output[o++] = ch; + } + + *osize_p -= o; + *isize_p -= i; + } + return 0; +} + +static struct stream_filter_vtbl lf_to_crlf_vtbl = { + lf_to_crlf_filter_fn, + null_free_fn, +}; + +static struct stream_filter lf_to_crlf_filter_singleton = { + &lf_to_crlf_vtbl, +}; + + +/* + * Cascade filter + */ +#define FILTER_BUFFER 1024 +struct cascade_filter { + struct stream_filter filter; + struct stream_filter *one; + struct stream_filter *two; + char buf[FILTER_BUFFER]; + int end, ptr; +}; + +static int cascade_filter_fn(struct stream_filter *filter, + const char *input, size_t *isize_p, + char *output, size_t *osize_p) +{ + struct cascade_filter *cas = (struct cascade_filter *) filter; + size_t filled = 0; + size_t sz = *osize_p; + size_t to_feed, remaining; + + /* + * input -- (one) --> buf -- (two) --> output + */ + while (filled < sz) { + remaining = sz - filled; + + /* do we already have something to feed two with? */ + if (cas->ptr < cas->end) { + to_feed = cas->end - cas->ptr; + if (stream_filter(cas->two, + cas->buf + cas->ptr, &to_feed, + output + filled, &remaining)) + return -1; + cas->ptr += (cas->end - cas->ptr) - to_feed; + filled = sz - remaining; + continue; + } + + /* feed one from upstream and have it emit into our buffer */ + to_feed = input ? *isize_p : 0; + if (input && !to_feed) + break; + remaining = sizeof(cas->buf); + if (stream_filter(cas->one, + input, &to_feed, + cas->buf, &remaining)) + return -1; + cas->end = sizeof(cas->buf) - remaining; + cas->ptr = 0; + if (input) { + size_t fed = *isize_p - to_feed; + *isize_p -= fed; + input += fed; + } + + /* do we know that we drained one completely? */ + if (input || cas->end) + continue; + + /* tell two to drain; we have nothing more to give it */ + to_feed = 0; + remaining = sz - filled; + if (stream_filter(cas->two, + NULL, &to_feed, + output + filled, &remaining)) + return -1; + if (remaining == (sz - filled)) + break; /* completely drained two */ + filled = sz - remaining; + } + *osize_p -= filled; + return 0; +} + +static void cascade_free_fn(struct stream_filter *filter) +{ + struct cascade_filter *cas = (struct cascade_filter *)filter; + free_stream_filter(cas->one); + free_stream_filter(cas->two); + free(filter); +} + +static struct stream_filter_vtbl cascade_vtbl = { + cascade_filter_fn, + cascade_free_fn, +}; + +static struct stream_filter *cascade_filter(struct stream_filter *one, + struct stream_filter *two) +{ + struct cascade_filter *cascade; + + if (!one || is_null_stream_filter(one)) + return two; + if (!two || is_null_stream_filter(two)) + return one; + + cascade = xmalloc(sizeof(*cascade)); + cascade->one = one; + cascade->two = two; + cascade->end = cascade->ptr = 0; + cascade->filter.vtbl = &cascade_vtbl; + return (struct stream_filter *)cascade; +} + +/* + * ident filter + */ +#define IDENT_DRAINING (-1) +#define IDENT_SKIPPING (-2) +struct ident_filter { + struct stream_filter filter; + struct strbuf left; + int state; + char ident[45]; /* ": x40 $" */ +}; + +static int is_foreign_ident(const char *str) +{ + int i; + + if (prefixcmp(str, "$Id: ")) + return 0; + for (i = 5; str[i]; i++) { + if (isspace(str[i]) && str[i+1] != '$') + return 1; + } + return 0; +} + +static void ident_drain(struct ident_filter *ident, char **output_p, size_t *osize_p) +{ + size_t to_drain = ident->left.len; + + if (*osize_p < to_drain) + to_drain = *osize_p; + if (to_drain) { + memcpy(*output_p, ident->left.buf, to_drain); + strbuf_remove(&ident->left, 0, to_drain); + *output_p += to_drain; + *osize_p -= to_drain; + } + if (!ident->left.len) + ident->state = 0; +} + +static int ident_filter_fn(struct stream_filter *filter, + const char *input, size_t *isize_p, + char *output, size_t *osize_p) +{ + struct ident_filter *ident = (struct ident_filter *)filter; + static const char head[] = "$Id"; + + if (!input) { + /* drain upon eof */ + switch (ident->state) { + default: + strbuf_add(&ident->left, head, ident->state); + case IDENT_SKIPPING: + /* fallthru */ + case IDENT_DRAINING: + ident_drain(ident, &output, osize_p); + } + return 0; + } + + while (*isize_p || (ident->state == IDENT_DRAINING)) { + int ch; + + if (ident->state == IDENT_DRAINING) { + ident_drain(ident, &output, osize_p); + if (!*osize_p) + break; + continue; + } + + ch = *(input++); + (*isize_p)--; + + if (ident->state == IDENT_SKIPPING) { + /* + * Skipping until '$' or LF, but keeping them + * in case it is a foreign ident. + */ + strbuf_addch(&ident->left, ch); + if (ch != '\n' && ch != '$') + continue; + if (ch == '$' && !is_foreign_ident(ident->left.buf)) { + strbuf_setlen(&ident->left, sizeof(head) - 1); + strbuf_addstr(&ident->left, ident->ident); + } + ident->state = IDENT_DRAINING; + continue; + } + + if (ident->state < sizeof(head) && + head[ident->state] == ch) { + ident->state++; + continue; + } + + if (ident->state) + strbuf_add(&ident->left, head, ident->state); + if (ident->state == sizeof(head) - 1) { + if (ch != ':' && ch != '$') { + strbuf_addch(&ident->left, ch); + ident->state = 0; + continue; + } + + if (ch == ':') { + strbuf_addch(&ident->left, ch); + ident->state = IDENT_SKIPPING; + } else { + strbuf_addstr(&ident->left, ident->ident); + ident->state = IDENT_DRAINING; + } + continue; + } + + strbuf_addch(&ident->left, ch); + ident->state = IDENT_DRAINING; + } + return 0; +} + +static void ident_free_fn(struct stream_filter *filter) +{ + struct ident_filter *ident = (struct ident_filter *)filter; + strbuf_release(&ident->left); + free(filter); +} + +static struct stream_filter_vtbl ident_vtbl = { + ident_filter_fn, + ident_free_fn, +}; + +static struct stream_filter *ident_filter(const unsigned char *sha1) +{ + struct ident_filter *ident = xmalloc(sizeof(*ident)); + + sprintf(ident->ident, ": %s $", sha1_to_hex(sha1)); + strbuf_init(&ident->left, 0); + ident->filter.vtbl = &ident_vtbl; + ident->state = 0; + return (struct stream_filter *)ident; +} + +/* + * Return an appropriately constructed filter for the path, or NULL if + * the contents cannot be filtered without reading the whole thing + * in-core. + * + * Note that you would be crazy to set CRLF, smuge/clean or ident to a + * large binary blob you would want us not to slurp into the memory! + */ +struct stream_filter *get_stream_filter(const char *path, const unsigned char *sha1) +{ + struct conv_attrs ca; + enum crlf_action crlf_action; + struct stream_filter *filter = NULL; + + convert_attrs(&ca, path); + + if (ca.drv && (ca.drv->smudge || ca.drv->clean)) + return filter; + + if (ca.ident) + filter = ident_filter(sha1); + + crlf_action = input_crlf_action(ca.crlf_action, ca.eol_attr); + + if ((crlf_action == CRLF_BINARY) || (crlf_action == CRLF_INPUT) || + (crlf_action == CRLF_GUESS && auto_crlf == AUTO_CRLF_FALSE)) + filter = cascade_filter(filter, &null_filter_singleton); + + else if (output_eol(crlf_action) == EOL_CRLF && + !(crlf_action == CRLF_AUTO || crlf_action == CRLF_GUESS)) + filter = cascade_filter(filter, &lf_to_crlf_filter_singleton); + + return filter; +} + +void free_stream_filter(struct stream_filter *filter) +{ + filter->vtbl->free(filter); +} + +int stream_filter(struct stream_filter *filter, + const char *input, size_t *isize_p, + char *output, size_t *osize_p) +{ + return filter->vtbl->filter(filter, input, isize_p, output, osize_p); +} diff --git a/convert.h b/convert.h new file mode 100644 index 000000000..d799a165b --- /dev/null +++ b/convert.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011, Google Inc. + */ +#ifndef CONVERT_H +#define CONVERT_H + +enum safe_crlf { + SAFE_CRLF_FALSE = 0, + SAFE_CRLF_FAIL = 1, + SAFE_CRLF_WARN = 2 +}; + +extern enum safe_crlf safe_crlf; + +enum auto_crlf { + AUTO_CRLF_FALSE = 0, + AUTO_CRLF_TRUE = 1, + AUTO_CRLF_INPUT = -1 +}; + +extern enum auto_crlf auto_crlf; + +enum eol { + EOL_UNSET, + EOL_CRLF, + EOL_LF, +#ifdef NATIVE_CRLF + EOL_NATIVE = EOL_CRLF +#else + EOL_NATIVE = EOL_LF +#endif +}; + +extern enum eol core_eol; + +/* returns 1 if *dst was used */ +extern int convert_to_git(const char *path, const char *src, size_t len, + struct strbuf *dst, enum safe_crlf checksafe); +extern int convert_to_working_tree(const char *path, const char *src, + size_t len, struct strbuf *dst); +extern int renormalize_buffer(const char *path, const char *src, size_t len, + struct strbuf *dst); + +/***************************************************************** + * + * Streaming converison support + * + *****************************************************************/ + +struct stream_filter; /* opaque */ + +extern struct stream_filter *get_stream_filter(const char *path, const unsigned char *); +extern void free_stream_filter(struct stream_filter *); +extern int is_null_stream_filter(struct stream_filter *); + +/* + * Use as much input up to *isize_p and fill output up to *osize_p; + * update isize_p and osize_p to indicate how much buffer space was + * consumed and filled. Return 0 on success, non-zero on error. + * + * Some filters may need to buffer the input and look-ahead inside it + * to decide what to output, and they may consume more than zero bytes + * of input and still not produce any output. After feeding all the + * input, pass NULL as input and keep calling this function, to let + * such filters know there is no more input coming and it is time for + * them to produce the remaining output based on the buffered input. + */ +extern int stream_filter(struct stream_filter *, + const char *input, size_t *isize_p, + char *output, size_t *osize_p); + +#endif /* CONVERT_H */ diff --git a/csum-file.c b/csum-file.c index be49d5fcf..fc97d6e04 100644 --- a/csum-file.c +++ b/csum-file.c @@ -11,8 +11,20 @@ #include "progress.h" #include "csum-file.h" -static void flush(struct sha1file *f, void * buf, unsigned int count) +static void flush(struct sha1file *f, void *buf, unsigned int count) { + if (0 <= f->check_fd && count) { + unsigned char check_buffer[8192]; + ssize_t ret = read_in_full(f->check_fd, check_buffer, count); + + if (ret < 0) + die_errno("%s: sha1 file read error", f->name); + if (ret < count) + die("%s: sha1 file truncated", f->name); + if (memcmp(buf, check_buffer, count)) + die("sha1 file '%s' validation error", f->name); + } + for (;;) { int ret = xwrite(f->fd, buf, count); if (ret > 0) { @@ -59,6 +71,17 @@ int sha1close(struct sha1file *f, unsigned char *result, unsigned int flags) fd = 0; } else fd = f->fd; + if (0 <= f->check_fd) { + char discard; + int cnt = read_in_full(f->check_fd, &discard, 1); + if (cnt < 0) + die_errno("%s: error when reading the tail of sha1 file", + f->name); + if (cnt) + die("%s: sha1 file has trailing garbage", f->name); + if (close(f->check_fd)) + die_errno("%s: sha1 file error on close", f->name); + } free(f); return fd; } @@ -101,10 +124,31 @@ struct sha1file *sha1fd(int fd, const char *name) return sha1fd_throughput(fd, name, NULL); } +struct sha1file *sha1fd_check(const char *name) +{ + int sink, check; + struct sha1file *f; + + sink = open("/dev/null", O_WRONLY); + if (sink < 0) + return NULL; + check = open(name, O_RDONLY); + if (check < 0) { + int saved_errno = errno; + close(sink); + errno = saved_errno; + return NULL; + } + f = sha1fd(sink, name); + f->check_fd = check; + return f; +} + struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp) { struct sha1file *f = xmalloc(sizeof(*f)); f->fd = fd; + f->check_fd = -1; f->offset = 0; f->total = 0; f->tp = tp; diff --git a/csum-file.h b/csum-file.h index 294add2a9..6a7967c6b 100644 --- a/csum-file.h +++ b/csum-file.h @@ -6,6 +6,7 @@ struct progress; /* A SHA1-protected file */ struct sha1file { int fd; + int check_fd; unsigned int offset; git_SHA_CTX ctx; off_t total; @@ -21,6 +22,7 @@ struct sha1file { #define CSUM_FSYNC 2 extern struct sha1file *sha1fd(int fd, const char *name); +extern struct sha1file *sha1fd_check(const char *name); extern struct sha1file *sha1fd_throughput(int fd, const char *name, struct progress *tp); extern int sha1close(struct sha1file *, unsigned char *, unsigned int); extern int sha1write(struct sha1file *, void *, unsigned int); diff --git a/diff-lib.c b/diff-lib.c index 9c29293bb..f8454dd29 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -379,7 +379,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, if (cached && idx && ce_stage(idx)) { struct diff_filepair *pair; pair = diff_unmerge(&revs->diffopt, idx->name); - fill_filespec(pair->one, idx->sha1, idx->ce_mode); + if (tree) + fill_filespec(pair->one, tree->sha1, tree->ce_mode); return; } @@ -433,26 +434,30 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) if (tree == o->df_conflict_entry) tree = NULL; - if (ce_path_match(idx ? idx : tree, &revs->prune_data)) + if (ce_path_match(idx ? idx : tree, &revs->prune_data)) { do_oneway_diff(o, idx, tree); + if (diff_can_quit_early(&revs->diffopt)) { + o->exiting_early = 1; + return -1; + } + } return 0; } -int run_diff_index(struct rev_info *revs, int cached) +static int diff_cache(struct rev_info *revs, + const unsigned char *tree_sha1, + const char *tree_name, + int cached) { - struct object *ent; struct tree *tree; - const char *tree_name; - struct unpack_trees_options opts; struct tree_desc t; + struct unpack_trees_options opts; - ent = revs->pending.objects[0].item; - tree_name = revs->pending.objects[0].name; - tree = parse_tree_indirect(ent->sha1); + tree = parse_tree_indirect(tree_sha1); if (!tree) - return error("bad tree object %s", tree_name); - + return error("bad tree object %s", + tree_name ? tree_name : sha1_to_hex(tree_sha1)); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; opts.index_only = cached; @@ -465,7 +470,15 @@ int run_diff_index(struct rev_info *revs, int cached) opts.dst_index = NULL; init_tree_desc(&t, tree->buffer, tree->size); - if (unpack_trees(1, &t, &opts)) + return unpack_trees(1, &t, &opts); +} + +int run_diff_index(struct rev_info *revs, int cached) +{ + struct object_array_entry *ent; + + ent = revs->pending.objects; + if (diff_cache(revs, ent->item->sha1, ent->name, cached)) exit(128); diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/"); @@ -477,53 +490,13 @@ int run_diff_index(struct rev_info *revs, int cached) int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) { - struct tree *tree; struct rev_info revs; - int i; - struct cache_entry **dst; - struct cache_entry *last = NULL; - struct unpack_trees_options opts; - struct tree_desc t; - - /* - * This is used by git-blame to run diff-cache internally; - * it potentially needs to repeatedly run this, so we will - * start by removing the higher order entries the last round - * left behind. - */ - dst = active_cache; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (ce_stage(ce)) { - if (last && !strcmp(ce->name, last->name)) - continue; - cache_tree_invalidate_path(active_cache_tree, - ce->name); - last = ce; - ce->ce_flags |= CE_REMOVE; - } - *dst++ = ce; - } - active_nr = dst - active_cache; init_revisions(&revs, NULL); init_pathspec(&revs.prune_data, opt->pathspec.raw); - tree = parse_tree_indirect(tree_sha1); - if (!tree) - die("bad tree object %s", sha1_to_hex(tree_sha1)); - - memset(&opts, 0, sizeof(opts)); - opts.head_idx = 1; - opts.index_only = 1; - opts.diff_index_cached = !DIFF_OPT_TST(opt, FIND_COPIES_HARDER); - opts.merge = 1; - opts.fn = oneway_diff; - opts.unpack_data = &revs; - opts.src_index = &the_index; - opts.dst_index = &the_index; + revs.diffopt = *opt; - init_tree_desc(&t, tree->buffer, tree->size); - if (unpack_trees(1, &t, &opts)) + if (diff_cache(&revs, tree_sha1, NULL, 1)) exit(128); return 0; } @@ -1316,9 +1316,10 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) int i, len, add, del, adds = 0, dels = 0; uintmax_t max_change = 0, max_len = 0; int total_files = data->nr; - int width, name_width; + int width, name_width, count; const char *reset, *add_c, *del_c; const char *line_prefix = ""; + int extra_shown = 0; struct strbuf *msg = NULL; if (data->nr == 0) @@ -1331,6 +1332,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) width = options->stat_width ? options->stat_width : 80; name_width = options->stat_name_width ? options->stat_name_width : 50; + count = options->stat_count ? options->stat_count : data->nr; /* Sanity: give at least 5 columns to the graph, * but leave at least 10 columns for the name. @@ -1347,9 +1349,14 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) add_c = diff_get_color_opt(options, DIFF_FILE_NEW); del_c = diff_get_color_opt(options, DIFF_FILE_OLD); - for (i = 0; i < data->nr; i++) { + for (i = 0; (i < count) && (i < data->nr); i++) { struct diffstat_file *file = data->files[i]; uintmax_t change = file->added + file->deleted; + if (!data->files[i]->is_renamed && + (change == 0)) { + count++; /* not shown == room for one more */ + continue; + } fill_print_name(file); len = strlen(file->print_name); if (max_len < len) @@ -1360,6 +1367,7 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) if (max_change < change) max_change = change; } + count = i; /* min(count, data->nr) */ /* Compute the width of the graph part; * 10 is for one blank at the beginning of the line plus @@ -1374,13 +1382,18 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) else width = max_change; - for (i = 0; i < data->nr; i++) { + for (i = 0; i < count; i++) { const char *prefix = ""; char *name = data->files[i]->print_name; uintmax_t added = data->files[i]->added; uintmax_t deleted = data->files[i]->deleted; int name_len; + if (!data->files[i]->is_renamed && + (added + deleted == 0)) { + total_files--; + continue; + } /* * "scale" the filename */ @@ -1415,11 +1428,6 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) fprintf(options->file, " Unmerged\n"); continue; } - else if (!data->files[i]->is_renamed && - (added + deleted == 0)) { - total_files--; - continue; - } /* * scale the add/delete @@ -1441,6 +1449,20 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) show_graph(options->file, '-', del, del_c, reset); fprintf(options->file, "\n"); } + for (i = count; i < data->nr; i++) { + uintmax_t added = data->files[i]->added; + uintmax_t deleted = data->files[i]->deleted; + if (!data->files[i]->is_renamed && + (added + deleted == 0)) { + total_files--; + continue; + } + adds += added; + dels += deleted; + if (!extra_shown) + fprintf(options->file, "%s ...\n", line_prefix); + extra_shown = 1; + } fprintf(options->file, "%s", line_prefix); fprintf(options->file, " %d files changed, %d insertions(+), %d deletions(-)\n", @@ -1839,20 +1861,20 @@ static unsigned char *deflate_it(char *data, { int bound; unsigned char *deflated; - z_stream stream; + git_zstream stream; memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - bound = deflateBound(&stream, size); + git_deflate_init(&stream, zlib_compression_level); + bound = git_deflate_bound(&stream, size); deflated = xmalloc(bound); stream.next_out = deflated; stream.avail_out = bound; stream.next_in = (unsigned char *)data; stream.avail_in = size; - while (deflate(&stream, Z_FINISH) == Z_OK) + while (git_deflate(&stream, Z_FINISH) == Z_OK) ; /* nothing */ - deflateEnd(&stream); + git_deflate_end(&stream); *result_size = stream.total_out; return deflated; } @@ -1984,19 +2006,7 @@ struct userdiff_driver *get_textconv(struct diff_filespec *one) return NULL; diff_filespec_load_driver(one); - if (!one->driver->textconv) - return NULL; - - if (one->driver->textconv_want_cache && !one->driver->textconv_cache) { - struct notes_cache *c = xmalloc(sizeof(*c)); - struct strbuf name = STRBUF_INIT; - - strbuf_addf(&name, "textconv/%s", one->driver->name); - notes_cache_init(c, name.buf, one->driver->textconv); - one->driver->textconv_cache = c; - } - - return one->driver; + return userdiff_get_textconv(one->driver); } static void builtin_diff(const char *name_a, @@ -3220,6 +3230,7 @@ static int stat_opt(struct diff_options *options, const char **av) char *end; int width = options->stat_width; int name_width = options->stat_name_width; + int count = options->stat_count; int argcount = 1; arg += strlen("--stat"); @@ -3247,12 +3258,24 @@ static int stat_opt(struct diff_options *options, const char **av) name_width = strtoul(av[1], &end, 10); argcount = 2; } + } else if (!prefixcmp(arg, "-count")) { + arg += strlen("-count"); + if (*arg == '=') + count = strtoul(arg + 1, &end, 10); + else if (!*arg && !av[1]) + die("Option '--stat-count' requires a value"); + else if (!*arg) { + count = strtoul(av[1], &end, 10); + argcount = 2; + } } break; case '=': width = strtoul(arg+1, &end, 10); if (*end == ',') name_width = strtoul(end+1, &end, 10); + if (*end == ',') + count = strtoul(end+1, &end, 10); } /* Important! This checks all the error cases! */ @@ -3261,6 +3284,7 @@ static int stat_opt(struct diff_options *options, const char **av) options->output_format |= DIFF_FORMAT_DIFFSTAT; options->stat_name_width = name_width; options->stat_width = width; + options->stat_count = count; return argcount; } @@ -3325,7 +3349,7 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) else if (!strcmp(arg, "-s")) options->output_format |= DIFF_FORMAT_NO_OUTPUT; else if (!prefixcmp(arg, "--stat")) - /* --stat, --stat-width, or --stat-name-width */ + /* --stat, --stat-width, --stat-name-width, or --stat-count */ return stat_opt(options, av); /* renames options */ @@ -125,6 +125,7 @@ struct diff_options { int stat_width; int stat_name_width; + int stat_count; const char *word_regex; enum diff_words_type word_diff; @@ -1,6 +1,7 @@ #include "cache.h" #include "blob.h" #include "dir.h" +#include "streaming.h" static void create_directories(const char *path, int path_len, const struct checkout *state) @@ -91,6 +92,91 @@ static void *read_blob_entry(struct cache_entry *ce, unsigned long *size) return NULL; } +static int open_output_fd(char *path, struct cache_entry *ce, int to_tempfile) +{ + int symlink = (ce->ce_mode & S_IFMT) != S_IFREG; + if (to_tempfile) { + strcpy(path, symlink + ? ".merge_link_XXXXXX" : ".merge_file_XXXXXX"); + return mkstemp(path); + } else { + return create_file(path, !symlink ? ce->ce_mode : 0666); + } +} + +static int fstat_output(int fd, const struct checkout *state, struct stat *st) +{ + /* use fstat() only when path == ce->name */ + if (fstat_is_reliable() && + state->refresh_cache && !state->base_dir_len) { + fstat(fd, st); + return 1; + } + return 0; +} + +static int streaming_write_entry(struct cache_entry *ce, char *path, + struct stream_filter *filter, + const struct checkout *state, int to_tempfile, + int *fstat_done, struct stat *statbuf) +{ + struct git_istream *st; + enum object_type type; + unsigned long sz; + int result = -1; + ssize_t kept = 0; + int fd = -1; + + st = open_istream(ce->sha1, &type, &sz, filter); + if (!st) + return -1; + if (type != OBJ_BLOB) + goto close_and_exit; + + fd = open_output_fd(path, ce, to_tempfile); + if (fd < 0) + goto close_and_exit; + + for (;;) { + char buf[1024 * 16]; + ssize_t wrote, holeto; + ssize_t readlen = read_istream(st, buf, sizeof(buf)); + + if (!readlen) + break; + if (sizeof(buf) == readlen) { + for (holeto = 0; holeto < readlen; holeto++) + if (buf[holeto]) + break; + if (readlen == holeto) { + kept += holeto; + continue; + } + } + + if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1) + goto close_and_exit; + else + kept = 0; + wrote = write_in_full(fd, buf, readlen); + + if (wrote != readlen) + goto close_and_exit; + } + if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 || + write(fd, "", 1) != 1)) + goto close_and_exit; + *fstat_done = fstat_output(fd, state, statbuf); + +close_and_exit: + close_istream(st); + if (0 <= fd) + result = close(fd); + if (result && 0 <= fd) + unlink(path); + return result; +} + static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile) { unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT; @@ -101,6 +187,15 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout size_t wrote, newsize = 0; struct stat st; + if (ce_mode_s_ifmt == S_IFREG) { + struct stream_filter *filter = get_stream_filter(path, ce->sha1); + if (filter && + !streaming_write_entry(ce, path, filter, + state, to_tempfile, + &fstat_done, &st)) + goto finish; + } + switch (ce_mode_s_ifmt) { case S_IFREG: case S_IFLNK: @@ -128,17 +223,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout size = newsize; } - if (to_tempfile) { - if (ce_mode_s_ifmt == S_IFREG) - strcpy(path, ".merge_file_XXXXXX"); - else - strcpy(path, ".merge_link_XXXXXX"); - fd = mkstemp(path); - } else if (ce_mode_s_ifmt == S_IFREG) { - fd = create_file(path, ce->ce_mode); - } else { - fd = create_file(path, 0666); - } + fd = open_output_fd(path, ce, to_tempfile); if (fd < 0) { free(new); return error("unable to create file %s (%s)", @@ -146,12 +231,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout } wrote = write_in_full(fd, new, size); - /* use fstat() only when path == ce->name */ - if (fstat_is_reliable() && - state->refresh_cache && !to_tempfile && !state->base_dir_len) { - fstat(fd, &st); - fstat_done = 1; - } + if (!to_tempfile) + fstat_done = fstat_output(fd, state, &st); close(fd); free(new); if (wrote != size) @@ -167,6 +248,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout return error("unknown file mode for %s in index", path); } +finish: if (state->refresh_cache) { if (!fstat_done) lstat(ce->name, &st); diff --git a/environment.c b/environment.c index 94d58fd24..19351024f 100644 --- a/environment.c +++ b/environment.c @@ -36,6 +36,7 @@ size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE; size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT; size_t delta_base_cache_limit = 16 * 1024 * 1024; unsigned long big_file_threshold = 512 * 1024 * 1024; +const char *log_pack_access; const char *pager_program; int pager_use_color = 1; const char *editor_program; diff --git a/fast-import.c b/fast-import.c index 78d978684..7cc22625e 100644 --- a/fast-import.c +++ b/fast-import.c @@ -304,6 +304,7 @@ static unsigned int atom_cnt; static struct atom_str **atom_table; /* The .pack file being generated */ +static struct pack_idx_option pack_idx_opts; static unsigned int pack_id; static struct sha1file *pack_file; static struct packed_git *pack_data; @@ -354,6 +355,7 @@ static unsigned int cmd_save = 100; static uintmax_t next_mark; static struct strbuf new_data = STRBUF_INIT; static int seen_data_command; +static int require_explicit_termination; /* Signal handling */ static volatile sig_atomic_t checkpoint_requested; @@ -896,7 +898,7 @@ static const char *create_index(void) if (c != last) die("internal consistency error creating the index"); - tmpfile = write_idx_file(NULL, idx, object_count, pack_data->sha1); + tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts, pack_data->sha1); free(idx); return tmpfile; } @@ -1017,7 +1019,7 @@ static int store_object( unsigned char sha1[20]; unsigned long hdrlen, deltalen; git_SHA_CTX c; - z_stream s; + git_zstream s; hdrlen = sprintf((char *)hdr,"%s %lu", typename(type), (unsigned long)dat->len) + 1; @@ -1050,7 +1052,7 @@ static int store_object( delta = NULL; memset(&s, 0, sizeof(s)); - deflateInit(&s, pack_compression_level); + git_deflate_init(&s, pack_compression_level); if (delta) { s.next_in = delta; s.avail_in = deltalen; @@ -1058,11 +1060,11 @@ static int store_object( s.next_in = (void *)dat->buf; s.avail_in = dat->len; } - s.avail_out = deflateBound(&s, s.avail_in); + s.avail_out = git_deflate_bound(&s, s.avail_in); s.next_out = out = xmalloc(s.avail_out); - while (deflate(&s, Z_FINISH) == Z_OK) - /* nothing */; - deflateEnd(&s); + while (git_deflate(&s, Z_FINISH) == Z_OK) + ; /* nothing */ + git_deflate_end(&s); /* Determine if we should auto-checkpoint. */ if ((max_packsize && (pack_size + 60 + s.total_out) > max_packsize) @@ -1078,14 +1080,14 @@ static int store_object( delta = NULL; memset(&s, 0, sizeof(s)); - deflateInit(&s, pack_compression_level); + git_deflate_init(&s, pack_compression_level); s.next_in = (void *)dat->buf; s.avail_in = dat->len; - s.avail_out = deflateBound(&s, s.avail_in); + s.avail_out = git_deflate_bound(&s, s.avail_in); s.next_out = out = xrealloc(out, s.avail_out); - while (deflate(&s, Z_FINISH) == Z_OK) - /* nothing */; - deflateEnd(&s); + while (git_deflate(&s, Z_FINISH) == Z_OK) + ; /* nothing */ + git_deflate_end(&s); } } @@ -1163,7 +1165,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) off_t offset; git_SHA_CTX c; git_SHA_CTX pack_file_ctx; - z_stream s; + git_zstream s; int status = Z_OK; /* Determine if we should auto-checkpoint. */ @@ -1187,7 +1189,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) crc32_begin(pack_file); memset(&s, 0, sizeof(s)); - deflateInit(&s, pack_compression_level); + git_deflate_init(&s, pack_compression_level); hdrlen = encode_in_pack_object_header(OBJ_BLOB, len, out_buf); if (out_sz <= hdrlen) @@ -1209,7 +1211,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) len -= n; } - status = deflate(&s, len ? 0 : Z_FINISH); + status = git_deflate(&s, len ? 0 : Z_FINISH); if (!s.avail_out || status == Z_STREAM_END) { size_t n = s.next_out - out_buf; @@ -1228,7 +1230,7 @@ static void stream_blob(uintmax_t len, unsigned char *sha1out, uintmax_t mark) die("unexpected deflate failure: %d", status); } } - deflateEnd(&s); + git_deflate_end(&s); git_SHA1_Final(sha1, &c); if (sha1out) @@ -3139,6 +3141,8 @@ static int parse_one_feature(const char *feature, int from_stream) relative_marks_paths = 1; } else if (!strcmp(feature, "no-relative-marks")) { relative_marks_paths = 0; + } else if (!strcmp(feature, "done")) { + require_explicit_termination = 1; } else if (!strcmp(feature, "force")) { force_update = 1; } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) { @@ -3195,10 +3199,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "pack.indexversion")) { - pack_idx_default_version = git_config_int(k, v); - if (pack_idx_default_version > 2) + pack_idx_opts.version = git_config_int(k, v); + if (pack_idx_opts.version > 2) die("bad pack.indexversion=%"PRIu32, - pack_idx_default_version); + pack_idx_opts.version); return 0; } if (!strcmp(k, "pack.packsizelimit")) { @@ -3252,6 +3256,7 @@ int main(int argc, const char **argv) usage(fast_import_usage); setup_git_directory(); + reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) pack_compression_level = core_compression_level; @@ -3288,6 +3293,8 @@ int main(int argc, const char **argv) parse_reset_branch(); else if (!strcmp("checkpoint", command_buf.buf)) parse_checkpoint(); + else if (!strcmp("done", command_buf.buf)) + break; else if (!prefixcmp(command_buf.buf, "progress ")) parse_progress(); else if (!prefixcmp(command_buf.buf, "feature ")) @@ -3307,6 +3314,9 @@ int main(int argc, const char **argv) if (!seen_data_command) parse_argv(); + if (require_explicit_termination && feof(stdin)) + die("stream ends early"); + end_packfile(); dump_branches(); @@ -37,13 +37,14 @@ rerere-autoupdate update the index with reused conflict resolution if possible rebasing* (internal use for git-rebase)" . git-sh-setup +. git-sh-i18n prefix=$(git rev-parse --show-prefix) set_reflog_action am require_work_tree cd_to_toplevel git var GIT_COMMITTER_IDENT >/dev/null || - die "You need to set your committer info first" + die "$(gettext "You need to set your committer info first")" if git rev-parse --verify -q HEAD >/dev/null then @@ -88,8 +89,11 @@ safe_to_abort () { then return 0 fi - echo >&2 "You seem to have moved HEAD since the last 'am' failure." - echo >&2 "Not rewinding to ORIG_HEAD" + ( + gettext "You seem to have moved HEAD since the last 'am' failure. +Not rewinding to ORIG_HEAD" && + echo + ) >&2 return 1 } @@ -98,9 +102,9 @@ stop_here_user_resolve () { printf '%s\n' "$resolvemsg" stop_here $1 fi - echo "When you have resolved this problem run \"$cmdline --resolved\"." - echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"." - echo "To restore the original branch and stop patching run \"$cmdline --abort\"." + eval_gettext "When you have resolved this problem run \"\$cmdline --resolved\". +If you would prefer to skip this patch, instead run \"\$cmdline --skip\". +To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo stop_here $1 } @@ -114,7 +118,7 @@ go_next () { cannot_fallback () { echo "$1" - echo "Cannot fall back to three-way merge." + gettext "Cannot fall back to three-way merge."; echo exit 1 } @@ -129,7 +133,7 @@ fall_back_3way () { "$dotest/patch" && GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ git write-tree >"$dotest/patch-merge-base+" || - cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge." + cannot_fallback "$(gettext "Repository lacks necessary blobs to fall back on 3-way merge.")" say Using index info to reconstruct a base tree... if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \ @@ -138,8 +142,8 @@ fall_back_3way () { mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base" mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index" else - cannot_fallback "Did you hand edit your patch? -It does not apply to blobs recorded in its index." + cannot_fallback "$(gettext "Did you hand edit your patch? +It does not apply to blobs recorded in its index.")" fi test -f "$dotest/patch-merge-index" && @@ -147,7 +151,7 @@ It does not apply to blobs recorded in its index." orig_tree=$(cat "$dotest/patch-merge-base") && rm -fr "$dotest"/patch-merge-* || exit 1 - say Falling back to patching base and 3-way merge... + say "$(gettext "Falling back to patching base and 3-way merge...")" # This is not so wrong. Depending on which base we picked, # orig_tree may be wildly different from ours, but his_tree @@ -254,7 +258,7 @@ split_patches () { stgit-series) if test $# -ne 1 then - clean_abort "Only one StGIT patch series can be applied at once" + clean_abort "$(gettext "Only one StGIT patch series can be applied at once")" fi series_dir=`dirname "$1"` series_file="$1" @@ -306,9 +310,9 @@ split_patches () { ;; *) if test -n "$parse_patch" ; then - clean_abort "Patch format $patch_format is not supported." + clean_abort "$(eval_gettext "Patch format \$patch_format is not supported.")" else - clean_abort "Patch format detection failed." + clean_abort "$(gettext "Patch format detection failed.")" fi ;; esac @@ -358,7 +362,7 @@ do --rebasing) rebasing=t threeway=t keep=t scissors=f no_inbody_headers=t ;; -d|--dotest) - die "-d option is no longer supported. Do not use." + die "$(gettext "-d option is no longer supported. Do not use.")" ;; --resolvemsg) shift; resolvemsg=$1 ;; @@ -421,12 +425,12 @@ then false ;; esac || - die "previous rebase directory $dotest still exists but mbox given." + die "$(eval_gettext "previous rebase directory \$dotest still exists but mbox given.")" resume=yes case "$skip,$abort" in t,t) - die "Please make up your mind. --skip or --abort?" + die "$(gettext "Please make up your mind. --skip or --abort?")" ;; t,) git rerere clear @@ -453,7 +457,7 @@ then else # Make sure we are not given --skip, --resolved, nor --abort test "$skip$resolved$abort" = "" || - die "Resolve operation not in progress, we are not resuming." + die "$(gettext "Resolve operation not in progress, we are not resuming.")" # Start afresh. mkdir -p "$dotest" || exit @@ -518,7 +522,7 @@ case "$resolved" in if test "$files" then test -n "$HAS_HEAD" && : >"$dotest/dirtyindex" - die "Dirty index: cannot apply patches (dirty: $files)" + die "$(eval_gettext "Dirty index: cannot apply patches (dirty: \$files)")" fi esac @@ -607,9 +611,9 @@ do go_next && continue test -s "$dotest/patch" || { - echo "Patch is empty. Was it split wrong?" - echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"." - echo "To restore the original branch and stop patching run \"$cmdline --abort\"." + eval_gettext "Patch is empty. Was it split wrong? +If you would prefer to skip this patch, instead run \"\$cmdline --skip\". +To restore the original branch and stop patching run \"\$cmdline --abort\"."; echo stop_here $this } rm -f "$dotest/original-commit" "$dotest/author-script" @@ -644,7 +648,7 @@ do if test -z "$GIT_AUTHOR_EMAIL" then - echo "Patch does not have a valid e-mail address." + gettext "Patch does not have a valid e-mail address."; echo stop_here $this fi @@ -691,15 +695,18 @@ do if test "$interactive" = t then test -t 0 || - die "cannot be interactive without stdin connected to a terminal." + die "$(gettext "cannot be interactive without stdin connected to a terminal.")" action=again while test "$action" = again do - echo "Commit Body is:" + gettext "Commit Body is:"; echo echo "--------------------------" cat "$dotest/final-commit" echo "--------------------------" - printf "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all " + # TRANSLATORS: Make sure to include [y], [n], [e], [v] and [a] + # in your translation. The program will only accept English + # input at this point. + gettext "Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all " read reply case "$reply" in [yY]*) action=yes ;; @@ -735,7 +742,7 @@ do stop_here $this fi - say "Applying: $FIRSTLINE" + say "$(eval_gettext "Applying: \$FIRSTLINE")" case "$resolved" in '') @@ -756,16 +763,16 @@ do # working tree. resolved= git diff-index --quiet --cached HEAD -- && { - echo "No changes - did you forget to use 'git add'?" - echo "If there is nothing left to stage, chances are that something else" - echo "already introduced the same changes; you might want to skip this patch." + gettext "No changes - did you forget to use 'git add'? +If there is nothing left to stage, chances are that something else +already introduced the same changes; you might want to skip this patch."; echo stop_here_user_resolve $this } unmerged=$(git ls-files -u) if test -n "$unmerged" then - echo "You still have unmerged paths in your index" - echo "did you forget to use 'git add'?" + gettext "You still have unmerged paths in your index +did you forget to use 'git add'?"; echo stop_here_user_resolve $this fi apply_status=0 @@ -780,7 +787,7 @@ do # Applying the patch to an earlier tree and merging the # result may have produced the same tree as ours. git diff-index --quiet --cached HEAD -- && { - say No changes -- Patch already applied. + say "$(gettext "No changes -- Patch already applied.")" go_next continue } @@ -790,7 +797,7 @@ do fi if test $apply_status != 0 then - printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE" + eval_gettext 'Patch failed at $msgnum $FIRSTLINE'; echo stop_here_user_resolve $this fi @@ -806,7 +813,7 @@ do GIT_AUTHOR_DATE= fi parent=$(git rev-parse --verify -q HEAD) || - say >&2 "applying to an empty history" + say >&2 "$(gettext "applying to an empty history")" if test -n "$committer_date_is_author_date" then diff --git a/git-bisect.sh b/git-bisect.sh index 415a8d04c..b2186a862 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -28,6 +28,7 @@ Please use "git help bisect" to get the full man page.' OPTIONS_SPEC= . git-sh-setup +. git-sh-i18n require_work_tree _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' @@ -35,10 +36,16 @@ _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" bisect_autostart() { test -s "$GIT_DIR/BISECT_START" || { - echo >&2 'You need to start by "git bisect start"' + ( + gettext "You need to start by \"git bisect start\"" && + echo + ) >&2 if test -t 0 then - echo >&2 -n 'Do you want me to do it for you [Y/n]? ' + # TRANSLATORS: Make sure to include [Y] and [n] in your + # translation. The program will only accept English input + # at this point. + gettext "Do you want me to do it for you [Y/n]? " >&2 read yesno case "$yesno" in [Nn]*) @@ -57,7 +64,7 @@ bisect_start() { # head=$(GIT_DIR="$GIT_DIR" git symbolic-ref -q HEAD) || head=$(GIT_DIR="$GIT_DIR" git rev-parse --verify HEAD) || - die "Bad HEAD - I need a HEAD" + die "$(gettext "Bad HEAD - I need a HEAD")" # # Check if we are bisecting. @@ -76,11 +83,11 @@ bisect_start() { # cogito usage, and cogito users should understand # it relates to cg-seek. [ -s "$GIT_DIR/head-name" ] && - die "won't bisect on seeked tree" + die "$(gettext "won't bisect on seeked tree")" start_head="${head#refs/heads/}" ;; *) - die "Bad HEAD - strange symbolic ref" + die "$(gettext "Bad HEAD - strange symbolic ref")" ;; esac fi @@ -110,7 +117,7 @@ bisect_start() { *) rev=$(git rev-parse -q --verify "$arg^{commit}") || { test $has_double_dash -eq 1 && - die "'$arg' does not appear to be a valid revision" + die "$(eval_gettext "'\$arg' does not appear to be a valid revision")" break } case $bad_seen in @@ -155,7 +162,7 @@ bisect_write() { case "$state" in bad) tag="$state" ;; good|skip) tag="$state"-"$rev" ;; - *) die "Bad bisect_write argument: $state" ;; + *) die "$(eval_gettext "Bad bisect_write argument: \$state")" ;; esac git update-ref "refs/bisect/$tag" "$rev" || exit echo "# $state: $(git show-branch $rev)" >>"$GIT_DIR/BISECT_LOG" @@ -183,7 +190,7 @@ bisect_skip() { do case "$arg" in *..*) - revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;; + revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;; *) revs=$(git rev-parse --sq-quote "$arg") ;; esac @@ -197,10 +204,10 @@ bisect_state() { state=$1 case "$#,$state" in 0,*) - die "Please call 'bisect_state' with at least one argument." ;; + die "$(gettext "Please call 'bisect_state' with at least one argument.")" ;; 1,bad|1,good|1,skip) rev=$(git rev-parse --verify HEAD) || - die "Bad rev input: HEAD" + die "$(gettext "Bad rev input: HEAD")" bisect_write "$state" "$rev" check_expected_revs "$rev" ;; 2,bad|*,good|*,skip) @@ -209,13 +216,13 @@ bisect_state() { for rev in "$@" do sha=$(git rev-parse --verify "$rev^{commit}") || - die "Bad rev input: $rev" + die "$(eval_gettext "Bad rev input: \$rev")" eval="$eval bisect_write '$state' '$sha'; " done eval "$eval" check_expected_revs "$@" ;; *,bad) - die "'git bisect bad' can take only one argument." ;; + die "$(gettext "'git bisect bad' can take only one argument.")" ;; *) usage ;; esac @@ -238,25 +245,38 @@ bisect_next_check() { t,,good) # have bad but not good. we could bisect although # this is less optimum. - echo >&2 'Warning: bisecting only with a bad commit.' + ( + gettext "Warning: bisecting only with a bad commit." && + echo + ) >&2 if test -t 0 then - printf >&2 'Are you sure [Y/n]? ' + # TRANSLATORS: Make sure to include [Y] and [n] in your + # translation. The program will only accept English input + # at this point. + gettext "Are you sure [Y/n]? " >&2 read yesno case "$yesno" in [Nn]*) exit 1 ;; esac fi : bisect without good... ;; *) - THEN='' - test -s "$GIT_DIR/BISECT_START" || { - echo >&2 'You need to start by "git bisect start".' - THEN='then ' - } - echo >&2 'You '$THEN'need to give me at least one good' \ - 'and one bad revisions.' - echo >&2 '(You can use "git bisect bad" and' \ - '"git bisect good" for that.)' + + if test -s "$GIT_DIR/BISECT_START" + then + ( + gettext "You need to give me at least one good and one bad revisions. +(You can use \"git bisect bad\" and \"git bisect good\" for that.)" && + echo + ) >&2 + else + ( + gettext "You need to start by \"git bisect start\". +You then need to give me at least one good and one bad revisions. +(You can use \"git bisect bad\" and \"git bisect good\" for that.)" && + echo + ) >&2 + fi exit 1 ;; esac } @@ -307,13 +327,15 @@ bisect_visualize() { bisect_reset() { test -s "$GIT_DIR/BISECT_START" || { - echo "We are not bisecting." + gettext "We are not bisecting."; echo return } case "$#" in 0) branch=$(cat "$GIT_DIR/BISECT_START") ;; - 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || - die "'$1' is not a valid commit" + 1) git rev-parse --quiet --verify "$1^{commit}" > /dev/null || { + invalid="$1" + die "$(eval_gettext "'\$invalid' is not a valid commit")" + } branch="$1" ;; *) usage ;; @@ -321,8 +343,8 @@ bisect_reset() { if git checkout "$branch" -- ; then bisect_clean_state else - die "Could not check out original HEAD '$branch'." \ - "Try 'git bisect reset <commit>'." + die "$(eval_gettext "Could not check out original HEAD '\$branch'. +Try 'git bisect reset <commit>'.")" fi } @@ -345,8 +367,9 @@ bisect_clean_state() { } bisect_replay () { - test "$#" -eq 1 || die "No logfile given" - test -r "$1" || die "cannot read $1 for replaying" + file="$1" + test "$#" -eq 1 || die "$(gettext "No logfile given")" + test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")" bisect_reset while read git bisect command rev do @@ -362,9 +385,9 @@ bisect_replay () { good|bad|skip) bisect_write "$command" "$rev" ;; *) - die "?? what are you talking about?" ;; + die "$(gettext "?? what are you talking about?")" ;; esac - done <"$1" + done <"$file" bisect_auto_next } @@ -373,14 +396,18 @@ bisect_run () { while true do - echo "running $@" + command="$@" + eval_gettext "running \$command"; echo "$@" res=$? # Check for really bad run error. if [ $res -lt 0 -o $res -ge 128 ]; then - echo >&2 "bisect run failed:" - echo >&2 "exit code $res from '$@' is < 0 or >= 128" + ( + eval_gettext "bisect run failed: +exit code \$res from '\$command' is < 0 or >= 128" && + echo + ) >&2 exit $res fi @@ -402,18 +429,24 @@ bisect_run () { if sane_grep "first bad commit could be any of" "$GIT_DIR/BISECT_RUN" \ > /dev/null; then - echo >&2 "bisect run cannot continue any more" + ( + gettext "bisect run cannot continue any more" && + echo + ) >&2 exit $res fi if [ $res -ne 0 ]; then - echo >&2 "bisect run failed:" - echo >&2 "'bisect_state $state' exited with error code $res" + ( + eval_gettext "bisect run failed: +'bisect_state \$state' exited with error code \$res" && + echo + ) >&2 exit $res fi if sane_grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then - echo "bisect run success" + gettext "bisect run success"; echo exit 0; fi @@ -421,7 +454,7 @@ bisect_run () { } bisect_log () { - test -s "$GIT_DIR/BISECT_LOG" || die "We are not bisecting." + test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")" cat "$GIT_DIR/BISECT_LOG" } diff --git a/git-compat-util.h b/git-compat-util.h index e0bb81ed8..ddfbf7714 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -215,10 +215,14 @@ extern char *gitbasename(char *); #define is_dir_sep(c) ((c) == '/') #endif +#ifndef find_last_dir_sep +#define find_last_dir_sep(path) strrchr(path, '/') +#endif + #if __HP_cc >= 61000 #define NORETURN __attribute__((noreturn)) #define NORETURN_PTR -#elif defined(__GNUC__) +#elif defined(__GNUC__) && !defined(NO_NORETURN) #define NORETURN __attribute__((__noreturn__)) #define NORETURN_PTR __attribute__((__noreturn__)) #elif defined(_MSC_VER) diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 962a93b58..6b5f2251b 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -363,7 +363,7 @@ while read commit parents; do sed -e '1,/^$/d' <../commit | \ eval "$filter_msg" > ../message || die "msg filter failed: $filter_msg" - @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \ + workdir=$workdir @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \ $(git write-tree) $parentstr < ../message > ../map/$commit || die "could not write rewritten commit" done <../revs diff --git a/git-instaweb.sh b/git-instaweb.sh index 8bfa8a055..01a1b05e6 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -27,6 +27,7 @@ httpd="$(git config --get instaweb.httpd)" root="$(git config --get instaweb.gitwebdir)" port=$(git config --get instaweb.port) module_path="$(git config --get instaweb.modulepath)" +action="browse" conf="$GIT_DIR/gitweb/httpd.conf" @@ -98,12 +99,18 @@ start_httpd () { # here $httpd should have a meaningful value resolve_full_httpd + mkdir -p "$fqgitdir/gitweb/$httpd_only" + conf="$fqgitdir/gitweb/$httpd_only.conf" + + # generate correct config file if it doesn't exist + test -f "$conf" || configure_httpd + test -f "$fqgitdir/gitweb/gitweb_config.perl" || gitweb_conf # don't quote $full_httpd, there can be arguments to it (-f) case "$httpd" in *mongoose*|*plackup*) #These servers don't have a daemon mode so we'll have to fork it - $full_httpd "$fqgitdir/gitweb/httpd.conf" & + $full_httpd "$conf" & #Save the pid before doing anything else (we'll print it later) pid=$! @@ -117,7 +124,7 @@ $pid EOF ;; *) - $full_httpd "$fqgitdir/gitweb/httpd.conf" + $full_httpd "$conf" if test $? != 0; then echo "Could not execute http daemon $httpd." exit 1 @@ -148,17 +155,13 @@ while test $# != 0 do case "$1" in --stop|stop) - stop_httpd - exit 0 + action="stop" ;; --start|start) - start_httpd - exit 0 + action="start" ;; --restart|restart) - stop_httpd - start_httpd - exit 0 + action="restart" ;; -l|--local) local=true @@ -587,33 +590,54 @@ our \$projects_list = \$projectroot; EOF } -gitweb_conf - -resolve_full_httpd -mkdir -p "$fqgitdir/gitweb/$httpd_only" +configure_httpd() { + case "$httpd" in + *lighttpd*) + lighttpd_conf + ;; + *apache2*|*httpd*) + apache2_conf + ;; + webrick) + webrick_conf + ;; + *mongoose*) + mongoose_conf + ;; + *plackup*) + plackup_conf + ;; + *) + echo "Unknown httpd specified: $httpd" + exit 1 + ;; + esac +} -case "$httpd" in -*lighttpd*) - lighttpd_conf - ;; -*apache2*|*httpd*) - apache2_conf - ;; -webrick) - webrick_conf +case "$action" in +stop) + stop_httpd + exit 0 ;; -*mongoose*) - mongoose_conf +start) + start_httpd + exit 0 ;; -*plackup*) - plackup_conf - ;; -*) - echo "Unknown httpd specified: $httpd" - exit 1 +restart) + stop_httpd + start_httpd + exit 0 ;; esac +gitweb_conf + +resolve_full_httpd +mkdir -p "$fqgitdir/gitweb/$httpd_only" +conf="$fqgitdir/gitweb/$httpd_only.conf" + +configure_httpd + start_httpd url=http://127.0.0.1:$port diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 4db921233..91f90acfb 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -86,6 +86,11 @@ get_merge_tool_cmd () { } run_merge_tool () { + # If GIT_PREFIX is empty then we cannot use it in tools + # that expect to be able to chdir() to its value. + GIT_PREFIX=${GIT_PREFIX:-.} + export GIT_PREFIX + merge_tool_path="$(get_merge_tool_path "$1")" || exit base_present="$2" status=0 @@ -188,6 +193,7 @@ run_merge_tool () { check_unchanged else "$merge_tool_path" -R -f -d -c "wincmd l" \ + -c 'cd $GIT_PREFIX' \ "$LOCAL" "$REMOTE" fi ;; @@ -199,6 +205,7 @@ run_merge_tool () { check_unchanged else "$merge_tool_path" -R -f -d -c "wincmd l" \ + -c 'cd $GIT_PREFIX' \ "$LOCAL" "$REMOTE" fi ;; diff --git a/git-mergetool.sh b/git-mergetool.sh index 3aab5aae8..3c157bcd2 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -100,7 +100,7 @@ resolve_deleted_merge () { else printf "Use (c)reated or (d)eleted file, or (a)bort? " fi - read ans + read ans || return 1 case "$ans" in [mMcC]*) git add -- "$MERGED" @@ -122,7 +122,7 @@ resolve_deleted_merge () { resolve_submodule_merge () { while true; do printf "Use (l)ocal or (r)emote, or (a)bort? " - read ans + read ans || return 1 case "$ans" in [lL]*) if ! local_present; then @@ -249,7 +249,7 @@ merge_file () { describe_file "$remote_mode" "remote" "$REMOTE" if "$prompt" = true; then printf "Hit return to start merge resolution tool (%s): " "$merge_tool" - read ans + read ans || return 1 fi if base_present; then @@ -320,7 +320,7 @@ done prompt_after_failed_merge() { while true; do printf "Continue merging other unresolved paths (y/n) ? " - read ans + read ans || return 1 case "$ans" in [yY]*) diff --git a/git-pull.sh b/git-pull.sh index fb9e2df93..a10b1290b 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -9,6 +9,7 @@ LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEA SUBDIRECTORY_OK=Yes OPTIONS_SPEC= . git-sh-setup +. git-sh-i18n set_reflog_action "pull $*" require_work_tree cd_to_toplevel @@ -17,20 +18,20 @@ cd_to_toplevel die_conflict () { git diff-index --cached --name-status -r --ignore-submodules HEAD -- if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then - die "Pull is not possible because you have unmerged files. + die "$(gettext "Pull is not possible because you have unmerged files. Please, fix them up in the work tree, and then use 'git add/rm <file>' -as appropriate to mark resolution, or use 'git commit -a'." +as appropriate to mark resolution, or use 'git commit -a'.")" else - die "Pull is not possible because you have unmerged files." + die "$(gettext "Pull is not possible because you have unmerged files.")" fi } die_merge () { if [ $(git config --bool --get advice.resolveConflict || echo true) = "true" ]; then - die "You have not concluded your merge (MERGE_HEAD exists). -Please, commit your changes before you can merge." + die "$(gettext "You have not concluded your merge (MERGE_HEAD exists). +Please, commit your changes before you can merge.")" else - die "You have not concluded your merge (MERGE_HEAD exists)." + die "$(gettext "You have not concluded your merge (MERGE_HEAD exists).")" fi } @@ -185,7 +186,7 @@ test true = "$rebase" && { # On an unborn branch if test -f "$GIT_DIR/index" then - die "updating an unborn branch with changes added to the index" + die "$(gettext "updating an unborn branch with changes added to the index")" fi else require_clean_work_tree "pull with rebase" "Please commit or stash them." @@ -216,17 +217,20 @@ then # $orig_head commit, but we are merging into $curr_head. # First update the working tree to match $curr_head. - echo >&2 "Warning: fetch updated the current branch head." - echo >&2 "Warning: fast-forwarding your working tree from" - echo >&2 "Warning: commit $orig_head." + ( + eval_gettext "Warning: fetch updated the current branch head. +Warning: fast-forwarding your working tree from +Warning: commit \$orig_head." && + echo + ) >&2 git update-index -q --refresh git read-tree -u -m "$orig_head" "$curr_head" || - die 'Cannot fast-forward your working tree. + die "$(eval_gettext "Cannot fast-forward your working tree. After making sure that you saved anything precious from -$ git diff '$orig_head' +$ git diff \$orig_head output, run $ git reset --hard -to recover.' +to recover.")" fi @@ -241,11 +245,11 @@ case "$merge_head" in ?*' '?*) if test -z "$orig_head" then - die "Cannot merge multiple branches into empty head" + die "$(gettext "Cannot merge multiple branches into empty head")" fi if test true = "$rebase" then - die "Cannot rebase onto multiple branches" + die "$(gettext "Cannot rebase onto multiple branches")" fi ;; esac diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 65690af89..c6ba7c155 100644 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -713,7 +713,6 @@ then # parents to rewrite and skipping dropped commits would # prematurely end our probe merges_option= - first_after_upstream="$(git rev-list --reverse --first-parent $upstream..$orig_head | head -n 1)" else merges_option="--no-merges --cherry-pick" fi @@ -746,7 +745,7 @@ do preserve=t for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) do - if test -f "$rewritten"/$p -a \( $p != $onto -o $sha1 = $first_after_upstream \) + if test -f "$rewritten"/$p then preserve=f fi diff --git a/git-rebase.sh b/git-rebase.sh index d7855ea1c..266a4c13b 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -13,7 +13,7 @@ It then attempts to create a new commit for each commit from the original It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure and run git rebase --continue. Another option is to bypass the commit -that caused the merge failure with git rebase --skip. To restore the +that caused the merge failure with git rebase --skip. To check out the original <branch> and remove the .git/rebase-apply working files, use the command git rebase --abort instead. @@ -57,9 +57,9 @@ whitespace=! passed to 'git apply' ignore-whitespace! passed to 'git apply' C=! passed to 'git apply' Actions: -continue! continue rebasing process -abort! abort rebasing process and restore original branch -skip! skip current patch and continue rebasing process +continue! continue +abort! abort and check out the original branch +skip! skip current patch and continue " . git-sh-setup set_reflog_action rebase @@ -72,7 +72,7 @@ ok_to_skip_pre_rebase= resolvemsg=" When you have resolved this problem run \"git rebase --continue\". If you would prefer to skip this patch, instead run \"git rebase --skip\". -To restore the original branch and stop rebasing run \"git rebase --abort\". +To check out the original branch and stop rebasing run \"git rebase --abort\". " unset onto strategy= @@ -441,8 +441,7 @@ case "$#" in then head_name="detached HEAD" else - echo >&2 "fatal: no such branch: $1" - usage + die "fatal: no such branch: $1" fi ;; *) diff --git a/git-remote-testgit.py b/git-remote-testgit.py index df9d512f1..e9c832bfd 100644 --- a/git-remote-testgit.py +++ b/git-remote-testgit.py @@ -35,7 +35,7 @@ def get_repo(alias, url): prefix = 'refs/testgit/%s/' % alias debug("prefix: '%s'", prefix) - repo.gitdir = "" + repo.gitdir = os.environ["GIT_DIR"] repo.alias = alias repo.prefix = prefix @@ -70,9 +70,19 @@ def do_capabilities(repo, args): print "import" print "export" - print "gitdir" print "refspec refs/heads/*:%s*" % repo.prefix + dirname = repo.get_base_path(repo.gitdir) + + if not os.path.exists(dirname): + os.makedirs(dirname) + + path = os.path.join(dirname, 'testgit.marks') + + print "*export-marks %s" % path + if os.path.exists(path): + print "*import-marks %s" % path + print # end capabilities @@ -121,8 +131,24 @@ def do_import(repo, args): if not repo.gitdir: die("Need gitdir to import") + ref = args[0] + refs = [ref] + + while True: + line = sys.stdin.readline() + if line == '\n': + break + if not line.startswith('import '): + die("Expected import line.") + + # strip of leading 'import ' + ref = line[7:].strip() + refs.append(ref) + repo = update_local_repo(repo) - repo.exporter.export_repo(repo.gitdir) + repo.exporter.export_repo(repo.gitdir, refs) + + print "done" def do_export(repo, args): @@ -132,32 +158,15 @@ def do_export(repo, args): if not repo.gitdir: die("Need gitdir to export") - dirname = repo.get_base_path(repo.gitdir) - - if not os.path.exists(dirname): - os.makedirs(dirname) - - path = os.path.join(dirname, 'testgit.marks') - print path - if os.path.exists(path): - print path - else: - print "" - sys.stdout.flush() - update_local_repo(repo) - repo.importer.do_import(repo.gitdir) - repo.non_local.push(repo.gitdir) - - -def do_gitdir(repo, args): - """Stores the location of the gitdir. - """ + changed = repo.importer.do_import(repo.gitdir) - if not args: - die("gitdir needs an argument") + if not repo.local: + repo.non_local.push(repo.gitdir) - repo.gitdir = ' '.join(args) + for ref in changed: + print "ok %s" % ref + print COMMANDS = { @@ -165,7 +174,6 @@ COMMANDS = { 'list': do_list, 'import': do_import, 'export': do_export, - 'gitdir': do_gitdir, } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 94e26ed5e..8e427dab3 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -39,9 +39,15 @@ git_broken_path_fix () { # @@BROKEN_PATH_FIX@@ -die() { - echo >&2 "$@" - exit 1 +die () { + die_with_status 1 "$@" +} + +die_with_status () { + status=$1 + shift + echo >&2 "$*" + exit "$status" } GIT_QUIET= diff --git a/git-stash.sh b/git-stash.sh index 0a9403653..f4e6f05ee 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -7,13 +7,15 @@ 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 [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] + or: $dashless [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] + [-u|--include-untracked] [-a|--all] [<message>]] or: $dashless clear" SUBDIRECTORY_OK=Yes OPTIONS_SPEC= START_DIR=`pwd` . git-sh-setup +. git-sh-i18n require_work_tree cd_to_toplevel @@ -33,13 +35,20 @@ fi no_changes () { git diff-index --quiet --cached HEAD --ignore-submodules -- && - git diff-files --quiet --ignore-submodules + git diff-files --quiet --ignore-submodules && + (test -z "$untracked" || test -z "$(untracked_files)") +} + +untracked_files () { + excl_opt=--exclude-standard + test "$untracked" = "all" && excl_opt= + git ls-files -o -z $excl_opt } clear_stash () { if test $# != 0 then - die "git stash clear with parameters is unimplemented" + die "$(gettext "git stash clear with parameters is unimplemented")" fi if current=$(git rev-parse --verify $ref_stash 2>/dev/null) then @@ -49,6 +58,7 @@ clear_stash () { create_stash () { stash_msg="$1" + untracked="$2" git update-index -q --refresh if no_changes @@ -61,7 +71,7 @@ create_stash () { then head=$(git rev-list --oneline -n 1 HEAD --) else - die "You do not have the initial commit yet" + die "$(gettext "You do not have the initial commit yet")" fi if branch=$(git symbolic-ref -q HEAD) @@ -76,7 +86,26 @@ create_stash () { i_tree=$(git write-tree) && i_commit=$(printf 'index on %s\n' "$msg" | git commit-tree $i_tree -p $b_commit) || - die "Cannot save the current index state" + die "$(gettext "Cannot save the current index state")" + + if test -n "$untracked" + then + # Untracked files are stored by themselves in a parentless commit, for + # ease of unpacking later. + u_commit=$( + untracked_files | ( + export GIT_INDEX_FILE="$TMPindex" + rm -f "$TMPindex" && + git update-index -z --add --remove --stdin && + u_tree=$(git write-tree) && + printf 'untracked files on %s\n' "$msg" | git commit-tree $u_tree && + rm -f "$TMPindex" + ) ) || die "Cannot save the untracked files" + + untracked_commit_option="-p $u_commit"; + else + untracked_commit_option= + fi if test -z "$patch_mode" then @@ -90,7 +119,7 @@ create_stash () { git write-tree && rm -f "$TMPindex" ) ) || - die "Cannot save the current worktree state" + die "$(gettext "Cannot save the current worktree state")" else @@ -103,14 +132,14 @@ create_stash () { # state of the working tree w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) || - die "Cannot save the current worktree state" + die "$(gettext "Cannot save the current worktree state")" git diff-tree -p HEAD $w_tree > "$TMP-patch" && test -s "$TMP-patch" || - die "No changes selected" + die "$(gettext "No changes selected")" rm -f "$TMP-index" || - die "Cannot remove temporary index (can't happen)" + die "$(gettext "Cannot remove temporary index (can't happen)")" fi @@ -122,13 +151,14 @@ create_stash () { stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg") fi w_commit=$(printf '%s\n' "$stash_msg" | - git commit-tree $w_tree -p $b_commit -p $i_commit) || - die "Cannot record working tree state" + git commit-tree $w_tree -p $b_commit -p $i_commit $untracked_commit_option) || + die "$(gettext "Cannot record working tree state")" } save_stash () { keep_index= patch_mode= + untracked= while test $# != 0 do case "$1" in @@ -146,13 +176,30 @@ save_stash () { -q|--quiet) GIT_QUIET=t ;; + -u|--include-untracked) + untracked=untracked + ;; + -a|--all) + untracked=all + ;; --) shift break ;; -*) - echo "error: unknown option for 'stash save': $1" - echo " To provide a message, use git stash save -- '$1'" + option="$1" + # TRANSLATORS: $option is an invalid option, like + # `--blah-blah'. The 7 spaces at the beginning of the + # second line correspond to "error: ". So you should line + # up the second line with however many characters the + # translation of "error: " takes in your language. E.g. in + # English this is: + # + # $ git stash save --blah-blah 2>&1 | head -n 2 + # error: unknown option for 'stash save': --blah-blah + # To provide a message, use git stash save -- '--blah-blah' + eval_gettext "$("error: unknown option for 'stash save': \$option + To provide a message, use git stash save -- '\$option'")"; echo usage ;; *) @@ -162,29 +209,39 @@ save_stash () { shift done + if test -n "$patch_mode" && test -n "$untracked" + then + die "Can't use --patch and ---include-untracked or --all at the same time" + fi + stash_msg="$*" git update-index -q --refresh if no_changes then - say 'No local changes to save' + say "$(gettext "No local changes to save")" exit 0 fi test -f "$GIT_DIR/logs/$ref_stash" || - clear_stash || die "Cannot initialize stash" + clear_stash || die "$(gettext "Cannot initialize stash")" - create_stash "$stash_msg" + create_stash "$stash_msg" $untracked # Make sure the reflog for stash is kept. : >>"$GIT_DIR/logs/$ref_stash" git update-ref -m "$stash_msg" $ref_stash $w_commit || - die "Cannot save the current status" + die "$(gettext "Cannot save the current status")" say Saved working directory and index state "$stash_msg" if test -z "$patch_mode" then git reset --hard ${GIT_QUIET:+-q} + test "$untracked" = "all" && CLEAN_X_OPTION=-x || CLEAN_X_OPTION= + if test -n "$untracked" + then + git clean --force --quiet $CLEAN_X_OPTION + fi if test "$keep_index" = "t" && test -n $i_tree then @@ -192,7 +249,7 @@ save_stash () { fi else git apply -R < "$TMP-patch" || - die "Cannot remove worktree changes" + die "$(gettext "Cannot remove worktree changes")" if test "$keep_index" != "t" then @@ -234,9 +291,11 @@ show_stash () { # w_commit is set to the commit containing the working tree # b_commit is set to the base commit # i_commit is set to the commit containing the index tree +# u_commit is set to the commit containing the untracked files tree # w_tree is set to the working tree # b_tree is set to the base tree # i_tree is set to the index tree +# u_tree is set to the untracked files tree # # GIT_QUIET is set to t if -q is specified # INDEX_OPTION is set to --index if --index is specified. @@ -261,9 +320,11 @@ parse_flags_and_rev() w_commit= b_commit= i_commit= + u_commit= w_tree= b_tree= i_tree= + u_tree= REV=$(git rev-parse --no-flags --symbolic "$@") || exit 1 @@ -287,18 +348,21 @@ parse_flags_and_rev() case $# in 0) - have_stash || die "No stash found." + have_stash || die "$(gettext "No stash found.")" set -- ${ref_stash}@{0} ;; 1) : ;; *) - die "Too many revisions specified: $REV" + die "$(eval_gettext "Too many revisions specified: \$REV")" ;; esac - REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || die "$1 is not valid reference" + REV=$(git rev-parse --quiet --symbolic --verify $1 2>/dev/null) || { + reference="$1" + die "$(eval_gettext "\$reference is not valid reference")" + } i_commit=$(git rev-parse --quiet --verify $REV^2 2>/dev/null) && set -- $(git rev-parse $REV $REV^1 $REV: $REV^1: $REV^2: 2>/dev/null) && @@ -311,6 +375,9 @@ parse_flags_and_rev() IS_STASH_LIKE=t && test "$ref_stash" = "$(git rev-parse --symbolic-full-name "${REV%@*}")" && IS_STASH_REF=t + + u_commit=$(git rev-parse --quiet --verify $REV^3 2>/dev/null) && + u_tree=$(git rev-parse $REV^3: 2>/dev/null) } is_stash_like() @@ -320,7 +387,10 @@ is_stash_like() } assert_stash_like() { - is_stash_like "$@" || die "'$*' is not a stash-like commit" + is_stash_like "$@" || { + args="$*" + die "$(eval_gettext "'\$args' is not a stash-like commit")" + } } is_stash_ref() { @@ -328,18 +398,21 @@ is_stash_ref() { } assert_stash_ref() { - is_stash_ref "$@" || die "'$*' is not a stash reference" + is_stash_ref "$@" || { + args="$*" + die "$(eval_gettext "'\$args' is not a stash reference")" + } } apply_stash () { assert_stash_like "$@" - git update-index -q --refresh || die 'unable to refresh index' + git update-index -q --refresh || die "$(gettext "unable to refresh index")" # current index state c_tree=$(git write-tree) || - die 'Cannot apply a stash in the middle of a merge' + die "$(gettext "Cannot apply a stash in the middle of a merge")" unstashed_index_tree= if test -n "$INDEX_OPTION" && test "$b_tree" != "$i_tree" && @@ -347,12 +420,20 @@ apply_stash () { then git diff-tree --binary $s^2^..$s^2 | git apply --cached test $? -ne 0 && - die 'Conflicts in index. Try without --index.' + die "$(gettext "Conflicts in index. Try without --index.")" unstashed_index_tree=$(git write-tree) || - die 'Could not save index tree' + die "$(gettext "Could not save index tree")" git reset fi + if test -n "$u_tree" + then + GIT_INDEX_FILE="$TMPindex" git-read-tree "$u_tree" && + GIT_INDEX_FILE="$TMPindex" git checkout-index --all && + rm -f "$TMPindex" || + die 'Could not restore untracked files from stash' + fi + eval " GITHEAD_$w_tree='Stashed changes' && GITHEAD_$c_tree='Updated upstream' && @@ -375,7 +456,7 @@ apply_stash () { git diff-index --cached --name-only --diff-filter=A $c_tree >"$a" && git read-tree --reset $c_tree && git update-index --add --stdin <"$a" || - die "Cannot unstage modified files" + die "$(gettext "Cannot unstage modified files")" rm -f "$a" fi squelch= @@ -389,7 +470,10 @@ apply_stash () { status=$? if test -n "$INDEX_OPTION" then - echo >&2 'Index was not unstashed.' + ( + gettext "Index was not unstashed." && + echo + ) >&2 fi exit $status fi @@ -406,14 +490,15 @@ drop_stash () { assert_stash_ref "$@" git reflog delete --updateref --rewrite "${REV}" && - say "Dropped ${REV} ($s)" || die "${REV}: Could not drop stash entry" + say "$(eval_gettext "Dropped \${REV} (\$s)")" || + die "$(eval_gettext "\${REV}: Could not drop stash entry")" # clear_stash if we just dropped the last stash entry git rev-parse --verify "$ref_stash@{0}" > /dev/null 2>&1 || clear_stash } apply_to_branch () { - test -n "$1" || die 'No branch name specified' + test -n "$1" || die "$(gettext "No branch name specified")" branch=$1 shift 1 @@ -484,7 +569,7 @@ branch) case $# in 0) save_stash && - say '(To restore them type "git stash apply")' + say "$(gettext "(To restore them type \"git stash apply\")")" ;; *) usage diff --git a/git-submodule.sh b/git-submodule.sh index d189a24c7..f46862f61 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -14,6 +14,7 @@ USAGE="[--quiet] add [-b branch] [-f|--force] [--reference <repository>] [--] <r or: $dashless [--quiet] sync [--] [<path>...]" OPTIONS_SPEC= . git-sh-setup +. git-sh-i18n . git-parse-remote require_work_tree @@ -34,7 +35,7 @@ resolve_relative_url () { remote=$(get_default_remote) remoteurl=$(git config "remote.$remote.url") || - die "remote ($remote) does not have a url defined in .git/config" + remoteurl=$(pwd) # the repository is its own authoritative upstream url="$1" remoteurl=${remoteurl%/} sep=/ @@ -52,7 +53,7 @@ resolve_relative_url () sep=: ;; *) - die "cannot strip one component off url '$remoteurl'" + die "$(eval_gettext "cannot strip one component off url '\$remoteurl'")" ;; esac ;; @@ -104,7 +105,7 @@ module_name() name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' | sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' ) test -z "$name" && - die "No submodule mapping found in .gitmodules for path '$path'" + die "$(eval_gettext "No submodule mapping found in .gitmodules for path '\$path'")" echo "$name" } @@ -121,14 +122,19 @@ module_clone() path=$1 url=$2 reference="$3" + quiet= + if test -n "$GIT_QUIET" + then + quiet=-q + fi if test -n "$reference" then - git-clone "$reference" -n "$url" "$path" + git-clone $quiet "$reference" -n "$url" "$path" else - git-clone -n "$url" "$path" + git-clone $quiet -n "$url" "$path" fi || - die "Clone of '$url' into submodule path '$path' failed" + die "$(eval_gettext "Clone of '\$url' into submodule path '\$path' failed")" } # @@ -201,7 +207,7 @@ cmd_add() realrepo=$repo ;; *) - die "repo URL: '$repo' must be absolute or begin with ./|../" + die "$(eval_gettext "repo URL: '\$repo' must be absolute or begin with ./|../")" ;; esac @@ -218,13 +224,16 @@ cmd_add() s|/*$|| ') git ls-files --error-unmatch "$path" > /dev/null 2>&1 && - die "'$path' already exists in the index" + die "$(eval_gettext "'\$path' already exists in the index")" if test -z "$force" && ! git add --dry-run --ignore-missing "$path" > /dev/null 2>&1 then - echo >&2 "The following path is ignored by one of your .gitignore files:" && - echo >&2 $path && - echo >&2 "Use -f if you really want to add it." + ( + eval_gettext "The following path is ignored by one of your .gitignore files: +\$path +Use -f if you really want to add it." && + echo + ) >&2 exit 1 fi @@ -233,20 +242,11 @@ cmd_add() then if test -d "$path"/.git -o -f "$path"/.git then - echo "Adding existing repo at '$path' to the index" + eval_gettext "Adding existing repo at '\$path' to the index"; echo else - die "'$path' already exists and is not a valid git repo" + die "$(eval_gettext "'\$path' already exists and is not a valid git repo")" fi - case "$repo" in - ./*|../*) - url=$(resolve_relative_url "$repo") || exit - ;; - *) - url="$repo" - ;; - esac - git config submodule."$path".url "$url" else module_clone "$path" "$realrepo" "$reference" || exit @@ -258,16 +258,17 @@ cmd_add() '') git checkout -f -q ;; ?*) git checkout -f -q -B "$branch" "origin/$branch" ;; esac - ) || die "Unable to checkout submodule '$path'" + ) || die "$(eval_gettext "Unable to checkout submodule '\$path'")" fi + git config submodule."$path".url "$realrepo" git add $force "$path" || - die "Failed to add submodule '$path'" + die "$(eval_gettext "Failed to add submodule '\$path'")" git config -f .gitmodules submodule."$path".path "$path" && git config -f .gitmodules submodule."$path".url "$repo" && git add --force .gitmodules || - die "Failed to register submodule '$path'" + die "$(eval_gettext "Failed to register submodule '\$path'")" } # @@ -300,12 +301,16 @@ cmd_foreach() toplevel=$(pwd) + # dup stdin so that it can be restored when running the external + # command in the subshell (and a recursive call to this function) + exec 3<&0 + module_list | while read mode sha1 stage path do if test -e "$path"/.git then - say "Entering '$prefix$path'" + say "$(eval_gettext "Entering '\$prefix\$path'")" name=$(module_name "$path") ( prefix="$prefix$path/" @@ -316,8 +321,8 @@ cmd_foreach() then cmd_foreach "--recursive" "$@" fi - ) || - die "Stopping at '$path'; script returned non-zero status." + ) <&3 3<&- || + die "$(eval_gettext "Stopping at '\$path'; script returned non-zero status.")" fi done } @@ -355,29 +360,30 @@ cmd_init() do # Skip already registered paths name=$(module_name "$path") || exit - url=$(git config submodule."$name".url) - test -z "$url" || continue - - url=$(git config -f .gitmodules submodule."$name".url) - test -z "$url" && - die "No url found for submodule path '$path' in .gitmodules" - - # Possibly a url relative to parent - case "$url" in - ./*|../*) - url=$(resolve_relative_url "$url") || exit - ;; - esac - - git config submodule."$name".url "$url" || - die "Failed to register url for submodule path '$path'" + if test -z "$(git config "submodule.$name.url")" + then + url=$(git config -f .gitmodules submodule."$name".url) + test -z "$url" && + die "$(eval_gettext "No url found for submodule path '\$path' in .gitmodules")" + + # Possibly a url relative to parent + case "$url" in + ./*|../*) + url=$(resolve_relative_url "$url") || exit + ;; + esac + git config submodule."$name".url "$url" || + die "$(eval_gettext "Failed to register url for submodule path '\$path'")" + fi + # Copy "update" setting when it is not set yet upd="$(git config -f .gitmodules submodule."$name".update)" test -z "$upd" || + test -n "$(git config submodule."$name".update)" || git config submodule."$name".update "$upd" || - die "Failed to register update mode for submodule path '$path'" + die "$(eval_gettext "Failed to register update mode for submodule path '\$path'")" - say "Submodule '$name' ($url) registered for path '$path'" + say "$(eval_gettext "Submodule '\$name' (\$url) registered for path '\$path'")" done } @@ -444,7 +450,8 @@ cmd_update() fi cloned_modules= - module_list "$@" | + module_list "$@" | { + err= while read mode sha1 stage path do if test "$stage" = U @@ -460,8 +467,8 @@ cmd_update() # Only mention uninitialized submodules when its # path have been specified test "$#" != "0" && - say "Submodule path '$path' not initialized" && - say "Maybe you want to use 'update --init'?" + say "$(eval_gettext "Submodule path '\$path' not initialized +Maybe you want to use 'update --init'?")" continue fi @@ -473,7 +480,7 @@ cmd_update() else subsha1=$(clear_local_git_env; cd "$path" && git rev-parse --verify HEAD) || - die "Unable to find current revision in submodule path '$path'" + die "$(eval_gettext "Unable to find current revision in submodule path '\$path'")" fi if ! test -z "$update" @@ -497,7 +504,7 @@ cmd_update() (clear_local_git_env; cd "$path" && ( (rev=$(git rev-list -n 1 $sha1 --not --all 2>/dev/null) && test -z "$rev") || git-fetch)) || - die "Unable to fetch in submodule path '$path'" + die "$(eval_gettext "Unable to fetch in submodule path '\$path'")" fi # Is this something we just cloned? @@ -507,35 +514,72 @@ cmd_update() update_module= ;; esac + must_die_on_failure= case "$update_module" in rebase) command="git rebase" - action="rebase" - msg="rebased onto" + die_msg="$(eval_gettext "Unable to rebase '\$sha1' in submodule path '\$path'")" + say_msg="$(eval_gettext "Submodule path '\$path': rebased into '\$sha1'")" + must_die_on_failure=yes ;; merge) command="git merge" - action="merge" - msg="merged in" + die_msg="$(eval_gettext "Unable to merge '\$sha1' in submodule path '\$path'")" + say_msg="$(eval_gettext "Submodule path '\$path': merged in '\$sha1'")" + must_die_on_failure=yes ;; *) command="git checkout $subforce -q" - action="checkout" - msg="checked out" + die_msg="$(eval_gettext "Unable to checkout '\$sha1' in submodule path '\$path'")" + say_msg="$(eval_gettext "Submodule path '\$path': checked out '\$sha1'")" ;; esac - (clear_local_git_env; cd "$path" && $command "$sha1") || - die "Unable to $action '$sha1' in submodule path '$path'" - say "Submodule path '$path': $msg '$sha1'" + if (clear_local_git_env; cd "$path" && $command "$sha1") + then + say "$say_msg" + elif test -n "$must_die_on_failure" + then + die_with_status 2 "$die_msg" + else + err="${err};$die_msg" + continue + fi fi if test -n "$recursive" then - (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") || - die "Failed to recurse into submodule path '$path'" + (clear_local_git_env; cd "$path" && eval cmd_update "$orig_flags") + res=$? + if test $res -gt 0 + then + die_msg="$(eval_gettext "Failed to recurse into submodule path '\$path'")" + if test $res -eq 1 + then + err="${err};$die_msg" + continue + else + die_with_status $res "$die_msg" + fi + fi fi done + + if test -n "$err" + then + OIFS=$IFS + IFS=';' + for e in $err + do + if test -n "$e" + then + echo >&2 "$e" + fi + done + IFS=$OIFS + exit 1 + fi + } } set_name_rev () { @@ -617,7 +661,7 @@ cmd_summary() { if [ -n "$files" ] then test -n "$cached" && - die "--cached cannot be used with --files" + die "$(gettext -- "--cached cannot be used with --files")" diff_cmd=diff-files head= fi @@ -657,7 +701,10 @@ cmd_summary() { ;; # removed *) # unexpected type - echo >&2 "unexpected mode $mod_dst" + ( + eval_gettext "unexpected mode \$mod_dst" && + echo + ) >&2 continue ;; esac fi @@ -675,13 +722,13 @@ cmd_summary() { total_commits= case "$missing_src,$missing_dst" in t,) - errmsg=" Warn: $name doesn't contain commit $sha1_src" + errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_src")" ;; ,t) - errmsg=" Warn: $name doesn't contain commit $sha1_dst" + errmsg="$(eval_gettext " Warn: \$name doesn't contain commit \$sha1_dst")" ;; t,t) - errmsg=" Warn: $name doesn't contain commits $sha1_src and $sha1_dst" + errmsg="$(eval_gettext " Warn: \$name doesn't contain commits \$sha1_src and \$sha1_dst")" ;; *) errmsg= @@ -706,11 +753,13 @@ cmd_summary() { sha1_abbr_dst=$(echo $sha1_dst | cut -c1-7) if test $status = T then + blob="$(gettext "blob")" + submodule="$(gettext "submodule")" if test $mod_dst = 160000 then - echo "* $name $sha1_abbr_src(blob)->$sha1_abbr_dst(submodule)$total_commits:" + echo "* $name $sha1_abbr_src($blob)->$sha1_abbr_dst($submodule)$total_commits:" else - echo "* $name $sha1_abbr_src(submodule)->$sha1_abbr_dst(blob)$total_commits:" + echo "* $name $sha1_abbr_src($submodule)->$sha1_abbr_dst($blob)$total_commits:" fi else echo "* $name $sha1_abbr_src...$sha1_abbr_dst$total_commits:" @@ -742,9 +791,9 @@ cmd_summary() { done | if test -n "$for_status"; then if [ -n "$files" ]; then - echo "# Submodules changed but not updated:" + gettext "# Submodules changed but not updated:"; echo else - echo "# Submodule changes to be committed:" + gettext "# Submodule changes to be committed:"; echo fi echo "#" sed -e 's|^|# |' -e 's|^# $|#|' @@ -830,7 +879,7 @@ cmd_status() cd "$path" && eval cmd_status "$orig_args" ) || - die "Failed to recurse into submodule path '$path'" + die "$(eval_gettext "Failed to recurse into submodule path '\$path'")" fi done } @@ -874,17 +923,20 @@ cmd_sync() ;; esac - say "Synchronizing submodule url for '$name'" - git config submodule."$name".url "$url" - - if test -e "$path"/.git + if git config "submodule.$name.url" >/dev/null 2>/dev/null then - ( - clear_local_git_env - cd "$path" - remote=$(get_default_remote) - git config remote."$remote".url "$url" - ) + say "$(eval_gettext "Synchronizing submodule url for '\$name'")" + git config submodule."$name".url "$url" + + if test -e "$path"/.git + then + ( + clear_local_git_env + cd "$path" + remote=$(get_default_remote) + git config remote."$remote".url "$url" + ) + fi fi done } @@ -183,8 +183,6 @@ static int handle_alias(int *argcp, const char ***argv) if (alias_string[0] == '!') { const char **alias_argv; int argc = *argcp, i; - struct strbuf sb = STRBUF_INIT; - const char *env[2]; commit_pager_choice(); @@ -195,13 +193,7 @@ static int handle_alias(int *argcp, const char ***argv) alias_argv[i] = (*argv)[i]; alias_argv[argc] = NULL; - strbuf_addstr(&sb, "GIT_PREFIX="); - if (subdir) - strbuf_addstr(&sb, subdir); - env[0] = sb.buf; - env[1] = NULL; - ret = run_command_v_opt_cd_env(alias_argv, RUN_USING_SHELL, NULL, env); - strbuf_release(&sb); + ret = run_command_v_opt(alias_argv, RUN_USING_SHELL); if (ret >= 0) /* normal exit */ exit(ret); diff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py index f40f9d6a2..9ee5f96d4 100644 --- a/git_remote_helpers/git/exporter.py +++ b/git_remote_helpers/git/exporter.py @@ -2,6 +2,8 @@ import os import subprocess import sys +from git_remote_helpers.util import check_call + class GitExporter(object): """An exporter for testgit repositories. @@ -15,7 +17,7 @@ class GitExporter(object): self.repo = repo - def export_repo(self, base): + def export_repo(self, base, refs=None): """Exports a fast-export stream for the given directory. Simply delegates to git fast-epxort and pipes it through sed @@ -23,8 +25,13 @@ class GitExporter(object): default refs/heads. This is to demonstrate how the export data can be stored under it's own ref (using the refspec capability). + + If None, refs defaults to ["HEAD"]. """ + if not refs: + refs = ["HEAD"] + dirname = self.repo.get_base_path(base) path = os.path.abspath(os.path.join(dirname, 'testgit.marks')) @@ -42,12 +49,10 @@ class GitExporter(object): if os.path.exists(path): args.append("--import-marks=" + path) - args.append("HEAD") + args.extend(refs) p1 = subprocess.Popen(args, stdout=subprocess.PIPE) args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"] - child = subprocess.Popen(args, stdin=p1.stdout) - if child.wait() != 0: - raise CalledProcessError + check_call(args, stdin=p1.stdout) diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py index 70a712729..5c6b595e1 100644 --- a/git_remote_helpers/git/importer.py +++ b/git_remote_helpers/git/importer.py @@ -1,6 +1,8 @@ import os import subprocess +from git_remote_helpers.util import check_call, check_output + class GitImporter(object): """An importer for testgit repositories. @@ -14,6 +16,18 @@ class GitImporter(object): self.repo = repo + def get_refs(self, gitdir): + """Returns a dictionary with refs. + """ + args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"] + lines = check_output(args).strip().split('\n') + refs = {} + for line in lines: + value, name = line.split(' ') + name = name.strip('commit\t') + refs[name] = value + return refs + def do_import(self, base): """Imports a fast-import stream to the given directory. @@ -30,11 +44,23 @@ class GitImporter(object): if not os.path.exists(dirname): os.makedirs(dirname) + refs_before = self.get_refs(gitdir) + args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path] if os.path.exists(path): args.append("--import-marks=" + path) - child = subprocess.Popen(args) - if child.wait() != 0: - raise CalledProcessError + check_call(args) + + refs_after = self.get_refs(gitdir) + + changed = {} + + for name, value in refs_after.iteritems(): + if refs_before.get(name) == value: + continue + + changed[name] = value + + return changed diff --git a/git_remote_helpers/git/non_local.py b/git_remote_helpers/git/non_local.py index f27389bb9..e70025095 100644 --- a/git_remote_helpers/git/non_local.py +++ b/git_remote_helpers/git/non_local.py @@ -1,7 +1,7 @@ import os import subprocess -from git_remote_helpers.util import die, warn +from git_remote_helpers.util import check_call, die, warn class NonLocalGit(object): @@ -29,9 +29,7 @@ class NonLocalGit(object): os.makedirs(path) args = ["git", "clone", "--bare", "--quiet", self.repo.gitpath, path] - child = subprocess.Popen(args) - if child.wait() != 0: - raise CalledProcessError + check_call(args) return path @@ -45,14 +43,10 @@ class NonLocalGit(object): die("could not find repo at %s", path) args = ["git", "--git-dir=" + path, "fetch", "--quiet", self.repo.gitpath] - child = subprocess.Popen(args) - if child.wait() != 0: - raise CalledProcessError + check_call(args) args = ["git", "--git-dir=" + path, "update-ref", "refs/heads/master", "FETCH_HEAD"] - child = subprocess.Popen(args) - if child.wait() != 0: - raise CalledProcessError + child = check_call(args) def push(self, base): """Pushes from the non-local repo to base. @@ -63,7 +57,5 @@ class NonLocalGit(object): if not os.path.exists(path): die("could not find repo at %s", path) - args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath] - child = subprocess.Popen(args) - if child.wait() != 0: - raise CalledProcessError + args = ["git", "--git-dir=" + path, "push", "--quiet", self.repo.gitpath, "--all"] + child = check_call(args) diff --git a/git_remote_helpers/git/repo.py b/git_remote_helpers/git/repo.py index 58e1cdb56..acbf8d778 100644 --- a/git_remote_helpers/git/repo.py +++ b/git_remote_helpers/git/repo.py @@ -1,6 +1,9 @@ import os import subprocess +from git_remote_helpers.util import check_call + + def sanitize(rev, sep='\t'): """Converts a for-each-ref line to a name/value pair. """ @@ -53,9 +56,7 @@ class GitRepo(object): path = ".cached_revs" ofile = open(path, "w") - child = subprocess.Popen(args, stdout=ofile) - if child.wait() != 0: - raise CalledProcessError + check_call(args, stdout=ofile) output = open(path).readlines() self.revmap = dict(sanitize(i) for i in output) if "HEAD" in self.revmap: diff --git a/git_remote_helpers/util.py b/git_remote_helpers/util.py index dce83e606..fbbb01b14 100644 --- a/git_remote_helpers/util.py +++ b/git_remote_helpers/util.py @@ -11,6 +11,21 @@ import sys import os import subprocess +try: + from subprocess import CalledProcessError +except ImportError: + # from python2.7:subprocess.py + # Exception classes used by this module. + class CalledProcessError(Exception): + """This exception is raised when a process run by check_call() returns + a non-zero exit status. The exit status will be stored in the + returncode attribute.""" + def __init__(self, returncode, cmd): + self.returncode = returncode + self.cmd = cmd + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + # Whether or not to show debug messages DEBUG = False @@ -128,6 +143,72 @@ def run_command (args, cwd = None, shell = False, add_env = None, return (exit_code, output, errors) +# from python2.7:subprocess.py +def call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete, then + return the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + retcode = call(["ls", "-l"]) + """ + return subprocess.Popen(*popenargs, **kwargs).wait() + + +# from python2.7:subprocess.py +def check_call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete. If + the exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) + """ + retcode = call(*popenargs, **kwargs) + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd) + return 0 + + +# from python2.7:subprocess.py +def check_output(*popenargs, **kwargs): + r"""Run command with arguments and return its output as a byte string. + + If the exit code was non-zero it raises a CalledProcessError. The + CalledProcessError object will have the return code in the returncode + attribute and output in the output attribute. + + The arguments are the same as for the Popen constructor. Example: + + >>> check_output(["ls", "-l", "/dev/null"]) + 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n' + + The stdout argument is not allowed as it is used internally. + To capture standard error in the result, use stderr=STDOUT. + + >>> check_output(["/bin/sh", "-c", + ... "ls -l non_existent_file ; exit 0"], + ... stderr=STDOUT) + 'ls: non_existent_file: No such file or directory\n' + """ + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise subprocess.CalledProcessError(retcode, cmd) + return output + + def file_reader_method (missing_ok = False): """Decorator for simplifying reading of files. diff --git a/gitweb/INSTALL b/gitweb/INSTALL index c5236fee9..f5efe7454 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -231,7 +231,7 @@ Gitweb config file See also "Runtime gitweb configuration" section in README file for gitweb (in gitweb/README). -- You can configure gitweb further using the gitweb configuration file; +- You can configure gitweb further using the per-instance gitweb configuration file; by default this is a file named gitweb_config.perl in the same place as gitweb.cgi script. You can control the default place for the config file using the GITWEB_CONFIG build configuration variable, and you can set it @@ -241,6 +241,17 @@ for gitweb (in gitweb/README). GITWEB_CONFIG_SYSTEM build configuration variable, and override it through the GITWEB_CONFIG_SYSTEM environment variable. + Note that if per-instance configuration file exists, then system-wide + configuration is _not used at all_. This is quite untypical and suprising + behavior. On the other hand changing current behavior would break backwards + compatibility and can lead to unexpected changes in gitweb behavior. + Therefore gitweb also looks for common system-wide configuration file, + normally /etc/gitweb-common.conf (set during build time using build time + configuration variable GITWEB_CONFIG_COMMON, set it at runtime using + environment variable with the same name). Settings from per-instance or + system-wide configuration file override those from common system-wide + configuration file. + - The gitweb config file is a fragment of perl code. You can set variables using "our $variable = value"; text from "#" character until the end of a line is ignored. See perlsyn(1) for details. diff --git a/gitweb/Makefile b/gitweb/Makefile index 5d20515fb..1c85b5fda 100644 --- a/gitweb/Makefile +++ b/gitweb/Makefile @@ -20,6 +20,7 @@ INSTALL ?= install # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl GITWEB_CONFIG_SYSTEM = /etc/gitweb.conf +GITWEB_CONFIG_COMMON = /etc/gitweb-common.conf GITWEB_HOME_LINK_STR = projects GITWEB_SITENAME = GITWEB_PROJECTROOT = /pub/git @@ -129,6 +130,7 @@ GITWEB_REPLACE = \ -e 's|++GIT_BINDIR++|$(bindir)|g' \ -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ -e 's|++GITWEB_CONFIG_SYSTEM++|$(GITWEB_CONFIG_SYSTEM)|g' \ + -e 's|++GITWEB_CONFIG_COMMON++|$(GITWEB_CONFIG_COMMON)|g' \ -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ diff --git a/gitweb/README b/gitweb/README index a4cfcb42c..a9988200d 100644 --- a/gitweb/README +++ b/gitweb/README @@ -10,9 +10,30 @@ From the git version 1.4.0 gitweb is bundled with git. Runtime gitweb configuration ---------------------------- -You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG` -(defaults to 'gitweb_config.perl' in the same directory as the CGI), and -as a fallback `GITWEB_CONFIG_SYSTEM` (defaults to /etc/gitweb.conf). +Gitweb obtains configuration data from the following sources in the +following order: + +1. built-in values (some set during build stage), +2. common system-wide configuration file (`GITWEB_CONFIG_COMMON`, + defaults to '/etc/gitweb-common.conf'), +3. either per-instance configuration file (`GITWEB_CONFIG`, defaults to + 'gitweb_config.perl' in the same directory as the installed gitweb), + or if it does not exists then system-wide configuration file + (`GITWEB_CONFIG_SYSTEM`, defaults to '/etc/gitweb.conf'). + +Values obtained in later configuration files override values obtained earlier +in above sequence. + +You can read defaults in system-wide GITWEB_CONFIG_SYSTEM from GITWEB_CONFIG +by adding + + read_config_file($GITWEB_CONFIG_SYSTEM); + +at very beginning of per-instance GITWEB_CONFIG file. In this case +settings in said per-instance file will override settings from +system-wide configuration file. Note that read_config_file checks +itself that the $GITWEB_CONFIG_SYSTEM file exists. + The most notable thing that is not configurable at compile time are the optional features, stored in the '%features' variable. diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 81dacf2b6..70a576a62 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -321,6 +321,10 @@ our %feature = ( # Enable text search, which will list the commits which match author, # committer or commit text to a given string. Enabled by default. # Project specific override is not supported. + # + # Note that this controls all search features, which means that if + # it is disabled, then 'grep' and 'pickaxe' search would also be + # disabled. 'search' => { 'override' => 0, 'default' => [1]}, @@ -661,13 +665,25 @@ sub read_config_file { return; } -our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM); +our ($GITWEB_CONFIG, $GITWEB_CONFIG_SYSTEM, $GITWEB_CONFIG_COMMON); sub evaluate_gitweb_config { our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; our $GITWEB_CONFIG_SYSTEM = $ENV{'GITWEB_CONFIG_SYSTEM'} || "++GITWEB_CONFIG_SYSTEM++"; + our $GITWEB_CONFIG_COMMON = $ENV{'GITWEB_CONFIG_COMMON'} || "++GITWEB_CONFIG_COMMON++"; + + # Protect agains duplications of file names, to not read config twice. + # Only one of $GITWEB_CONFIG and $GITWEB_CONFIG_SYSTEM is used, so + # there possibility of duplication of filename there doesn't matter. + $GITWEB_CONFIG = "" if ($GITWEB_CONFIG eq $GITWEB_CONFIG_COMMON); + $GITWEB_CONFIG_SYSTEM = "" if ($GITWEB_CONFIG_SYSTEM eq $GITWEB_CONFIG_COMMON); + + # Common system-wide settings for convenience. + # Those settings can be ovverriden by GITWEB_CONFIG or GITWEB_CONFIG_SYSTEM. + read_config_file($GITWEB_CONFIG_COMMON); - # use first config file that exists - read_config_file($GITWEB_CONFIG) or + # Use first config file that exists. This means use the per-instance + # GITWEB_CONFIG if exists, otherwise use GITWEB_SYSTEM_CONFIG. + read_config_file($GITWEB_CONFIG) and return; read_config_file($GITWEB_CONFIG_SYSTEM); } @@ -2510,6 +2526,13 @@ sub git_get_project_config { # key sanity check return unless ($key); + # only subsection, if exists, is case sensitive, + # and not lowercased by 'git config -z -l' + if (my ($hi, $mi, $lo) = ($key =~ /^([^.]*)\.(.*)\.([^.]*)$/)) { + $key = join(".", lc($hi), $mi, lc($lo)); + } else { + $key = lc($key); + } $key =~ s/^gitweb\.//; return if ($key =~ m/\W/); @@ -3562,12 +3585,9 @@ sub mimetype_guess_file { open(my $mh, '<', $mimemap) or return undef; while (<$mh>) { next if m/^#/; # skip comments - my ($mimetype, $exts) = split(/\t+/); - if (defined $exts) { - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mimetype; - } + my ($mimetype, @exts) = split(/\s+/); + foreach my $ext (@exts) { + $mimemap{$ext} = $mimetype; } } close($mh); @@ -3683,6 +3703,20 @@ sub get_page_title { return $title; } +sub get_content_type_html { + # require explicit support from the UA if we are to send the page as + # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. + # we have to do this because MSIE sometimes globs '*/*', pretending to + # support xhtml+xml but choking when it gets what it asked for. + if (defined $cgi->http('HTTP_ACCEPT') && + $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && + $cgi->Accept('application/xhtml+xml') != 0) { + return 'application/xhtml+xml'; + } else { + return 'text/html'; + } +} + sub print_feed_meta { if (defined $project) { my %href_params = get_feed_info(); @@ -3728,24 +3762,90 @@ sub print_feed_meta { } } +sub print_header_links { + my $status = shift; + + # print out each stylesheet that exist, providing backwards capability + # for those people who defined $stylesheet in a config file + if (defined $stylesheet) { + print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; + } else { + foreach my $stylesheet (@stylesheets) { + next unless $stylesheet; + print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; + } + } + print_feed_meta() + if ($status eq '200 OK'); + if (defined $favicon) { + print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n); + } +} + +sub print_nav_breadcrumbs { + my %opts = @_; + + print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; + if (defined $project) { + print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); + if (defined $action) { + my $action_print = $action ; + if (defined $opts{-action_extra}) { + $action_print = $cgi->a({-href => href(action=>$action)}, + $action); + } + print " / $action_print"; + } + if (defined $opts{-action_extra}) { + print " / $opts{-action_extra}"; + } + print "\n"; + } +} + +sub print_search_form { + if (!defined $searchtext) { + $searchtext = ""; + } + my $search_hash; + if (defined $hash_base) { + $search_hash = $hash_base; + } elsif (defined $hash) { + $search_hash = $hash; + } else { + $search_hash = "HEAD"; + } + my $action = $my_uri; + my $use_pathinfo = gitweb_check_feature('pathinfo'); + if ($use_pathinfo) { + $action .= "/".esc_url($project); + } + print $cgi->startform(-method => "get", -action => $action) . + "<div class=\"search\">\n" . + (!$use_pathinfo && + $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") . + $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" . + $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" . + $cgi->popup_menu(-name => 'st', -default => 'commit', + -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) . + $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . + " search:\n", + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "<span title=\"Extended regular expression\">" . + $cgi->checkbox(-name => 'sr', -value => 1, -label => 're', + -checked => $search_use_regexp) . + "</span>" . + "</div>" . + $cgi->end_form() . "\n"; +} + sub git_header_html { my $status = shift || "200 OK"; my $expires = shift; my %opts = @_; my $title = get_page_title(); - my $content_type; - # require explicit support from the UA if we are to send the page as - # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. - # we have to do this because MSIE sometimes globs '*/*', pretending to - # support xhtml+xml but choking when it gets what it asked for. - if (defined $cgi->http('HTTP_ACCEPT') && - $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && - $cgi->Accept('application/xhtml+xml') != 0) { - $content_type = 'application/xhtml+xml'; - } else { - $content_type = 'text/html'; - } + my $content_type = get_content_type_html(); print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires) unless ($opts{'-no_http_header'}); @@ -3767,22 +3867,7 @@ EOF if ($ENV{'PATH_INFO'}) { print "<base href=\"".esc_url($base_url)."\" />\n"; } - # print out each stylesheet that exist, providing backwards capability - # for those people who defined $stylesheet in a config file - if (defined $stylesheet) { - print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; - } else { - foreach my $stylesheet (@stylesheets) { - next unless $stylesheet; - print '<link rel="stylesheet" type="text/css" href="'.esc_url($stylesheet).'"/>'."\n"; - } - } - print_feed_meta() - if ($status eq '200 OK'); - if (defined $favicon) { - print qq(<link rel="shortcut icon" href=").esc_url($favicon).qq(" type="image/png" />\n); - } - + print_header_links($status); print "</head>\n" . "<body>\n"; @@ -3799,59 +3884,12 @@ EOF -alt => "git", -class => "logo"})); } - print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / "; - if (defined $project) { - print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); - if (defined $action) { - my $action_print = $action ; - if (defined $opts{-action_extra}) { - $action_print = $cgi->a({-href => href(action=>$action)}, - $action); - } - print " / $action_print"; - } - if (defined $opts{-action_extra}) { - print " / $opts{-action_extra}"; - } - print "\n"; - } + print_nav_breadcrumbs(%opts); print "</div>\n"; my $have_search = gitweb_check_feature('search'); if (defined $project && $have_search) { - if (!defined $searchtext) { - $searchtext = ""; - } - my $search_hash; - if (defined $hash_base) { - $search_hash = $hash_base; - } elsif (defined $hash) { - $search_hash = $hash; - } else { - $search_hash = "HEAD"; - } - my $action = $my_uri; - my $use_pathinfo = gitweb_check_feature('pathinfo'); - if ($use_pathinfo) { - $action .= "/".esc_url($project); - } - print $cgi->startform(-method => "get", -action => $action) . - "<div class=\"search\">\n" . - (!$use_pathinfo && - $cgi->input({-name=>"p", -value=>$project, -type=>"hidden"}) . "\n") . - $cgi->input({-name=>"a", -value=>"search", -type=>"hidden"}) . "\n" . - $cgi->input({-name=>"h", -value=>$search_hash, -type=>"hidden"}) . "\n" . - $cgi->popup_menu(-name => 'st', -default => 'commit', - -values => ['commit', 'grep', 'author', 'committer', 'pickaxe']) . - $cgi->sup($cgi->a({-href => href(action=>"search_help")}, "?")) . - " search:\n", - $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . - "<span title=\"Extended regular expression\">" . - $cgi->checkbox(-name => 'sr', -value => 1, -label => 're', - -checked => $search_use_regexp) . - "</span>" . - "</div>" . - $cgi->end_form() . "\n"; + print_search_form(); } } @@ -5506,6 +5544,216 @@ sub git_remotes_body { } } +sub git_search_message { + my %co = @_; + + my $greptype; + if ($searchtype eq 'commit') { + $greptype = "--grep="; + } elsif ($searchtype eq 'author') { + $greptype = "--author="; + } elsif ($searchtype eq 'committer') { + $greptype = "--committer="; + } + $greptype .= $searchtext; + my @commitlist = parse_commits($hash, 101, (100 * $page), undef, + $greptype, '--regexp-ignore-case', + $search_use_regexp ? '--extended-regexp' : '--fixed-strings'); + + my $paging_nav = ''; + if ($page > 0) { + $paging_nav .= + $cgi->a({-href => href(-replay=>1, page=>undef)}, + "first") . + " ⋅ " . + $cgi->a({-href => href(-replay=>1, page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= "first ⋅ prev"; + } + my $next_link = ''; + if ($#commitlist >= 100) { + $next_link = + $cgi->a({-href => href(-replay=>1, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + $paging_nav .= " ⋅ $next_link"; + } else { + $paging_nav .= " ⋅ next"; + } + + git_header_html(); + + git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + if ($page == 0 && !@commitlist) { + print "<p>No match.</p>\n"; + } else { + git_search_grep_body(\@commitlist, 0, 99, $next_link); + } + + git_footer_html(); +} + +sub git_search_changes { + my %co = @_; + + local $/ = "\n"; + open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts, + '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext", + ($search_use_regexp ? '--pickaxe-regex' : ()) + or die_error(500, "Open git-log failed"); + + git_header_html(); + + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + + print "<table class=\"pickaxe search\">\n"; + my $alternate = 1; + undef %co; + my @files; + while (my $line = <$fd>) { + chomp $line; + next unless $line; + + my %set = parse_difftree_raw_line($line); + if (defined $set{'commit'}) { + # finish previous commit + if (%co) { + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, + "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, + hash_base=>$co{'id'})}, + "tree") . + "</td>\n" . + "</tr>\n"; + } + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + %co = parse_commit($set{'commit'}); + my $author = chop_and_escape_str($co{'author_name'}, 15, 5); + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + "<td><i>$author</i></td>\n" . + "<td>" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), + -class => "list subject"}, + chop_and_escape_str($co{'title'}, 50) . "<br/>"); + } elsif (defined $set{'to_id'}) { + next if ($set{'to_id'} =~ m/^0{40}$/); + + print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, + hash=>$set{'to_id'}, file_name=>$set{'to_file'}), + -class => "list"}, + "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") . + "<br/>\n"; + } + } + close $fd; + + # finish last commit (warning: repetition!) + if (%co) { + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, + "commit") . + " | " . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, + hash_base=>$co{'id'})}, + "tree") . + "</td>\n" . + "</tr>\n"; + } + + print "</table>\n"; + + git_footer_html(); +} + +sub git_search_files { + my %co = @_; + + local $/ = "\n"; + open my $fd, "-|", git_cmd(), 'grep', '-n', + $search_use_regexp ? ('-E', '-i') : '-F', + $searchtext, $co{'tree'} + or die_error(500, "Open git-grep failed"); + + git_header_html(); + + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); + + print "<table class=\"grep_search\">\n"; + my $alternate = 1; + my $matches = 0; + my $lastfile = ''; + while (my $line = <$fd>) { + chomp $line; + my ($file, $lno, $ltext, $binary); + last if ($matches++ > 1000); + if ($line =~ /^Binary file (.+) matches$/) { + $file = $1; + $binary = 1; + } else { + (undef, $file, $lno, $ltext) = split(/:/, $line, 4); + } + if ($file ne $lastfile) { + $lastfile and print "</td></tr>\n"; + if ($alternate++) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + print "<td class=\"list\">". + $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'}, + file_name=>"$file"), + -class => "list"}, esc_path($file)); + print "</td><td>\n"; + $lastfile = $file; + } + if ($binary) { + print "<div class=\"binary\">Binary file</div>\n"; + } else { + $ltext = untabify($ltext); + if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) { + $ltext = esc_html($1, -nbsp=>1); + $ltext .= '<span class="match">'; + $ltext .= esc_html($2, -nbsp=>1); + $ltext .= '</span>'; + $ltext .= esc_html($3, -nbsp=>1); + } else { + $ltext = esc_html($ltext, -nbsp=>1); + } + print "<div class=\"pre\">" . + $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'}, + file_name=>"$file").'#l'.$lno, + -class => "linenr"}, sprintf('%4i', $lno)) + . ' ' . $ltext . "</div>\n"; + } + } + if ($lastfile) { + print "</td></tr>\n"; + if ($matches > 1000) { + print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n"; + } + } else { + print "<div class=\"diff nodifferences\">No matches found</div>\n"; + } + close $fd; + + print "</table>\n"; + + git_footer_html(); +} + sub git_search_grep_body { my ($commitlist, $from, $to, $extra) = @_; $from = 0 unless defined $from; @@ -6129,7 +6377,16 @@ sub git_blob_plain { # want to be sure not to break that by serving the image as an # attachment (though Firefox 3 doesn't seem to care). my $sandbox = $prevent_xss && - $type !~ m!^(?:text/plain|image/(?:gif|png|jpeg))(?:[ ;]|$)!; + $type !~ m!^(?:text/[a-z]+|image/(?:gif|png|jpeg))(?:[ ;]|$)!; + + # serve text/* as text/plain + if ($prevent_xss && + ($type =~ m!^text/[a-z]+\b(.*)$! || + ($type =~ m!^[a-z]+/[a-z]\+xml\b(.*)$! && -T $fd))) { + my $rest = $1; + $rest = defined $rest ? $rest : ''; + $type = "text/plain$rest"; + } print $cgi->header( -type => $type, @@ -6227,7 +6484,7 @@ sub git_blob { $nr++; $line = untabify($line); printf qq!<div class="pre"><a id="l%i" href="%s#l%i" class="linenr">%4i</a> %s</div>\n!, - $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? $line : esc_html($line, -nbsp=>1); + $nr, esc_attr(href(-replay => 1)), $nr, $nr, $syntax ? to_utf8($line) : esc_html($line, -nbsp=>1); } } close $fd @@ -7052,7 +7309,23 @@ sub git_history { } sub git_search { - gitweb_check_feature('search') or die_error(403, "Search is disabled"); + $searchtype ||= 'commit'; + + # check if appropriate features are enabled + gitweb_check_feature('search') + or die_error(403, "Search is disabled"); + if ($searchtype eq 'pickaxe') { + # pickaxe may take all resources of your box and run for several minutes + # with every query - so decide by yourself how public you make this feature + gitweb_check_feature('pickaxe') + or die_error(403, "Pickaxe search is disabled"); + } + if ($searchtype eq 'grep') { + # grep search might be potentially CPU-intensive, too + gitweb_check_feature('grep') + or die_error(403, "Grep search is disabled"); + } + if (!defined $searchtext) { die_error(400, "Text field is empty"); } @@ -7067,205 +7340,17 @@ sub git_search { $page = 0; } - $searchtype ||= 'commit'; - if ($searchtype eq 'pickaxe') { - # pickaxe may take all resources of your box and run for several minutes - # with every query - so decide by yourself how public you make this feature - gitweb_check_feature('pickaxe') - or die_error(403, "Pickaxe is disabled"); - } - if ($searchtype eq 'grep') { - gitweb_check_feature('grep') - or die_error(403, "Grep is disabled"); - } - - git_header_html(); - - if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') { - my $greptype; - if ($searchtype eq 'commit') { - $greptype = "--grep="; - } elsif ($searchtype eq 'author') { - $greptype = "--author="; - } elsif ($searchtype eq 'committer') { - $greptype = "--committer="; - } - $greptype .= $searchtext; - my @commitlist = parse_commits($hash, 101, (100 * $page), undef, - $greptype, '--regexp-ignore-case', - $search_use_regexp ? '--extended-regexp' : '--fixed-strings'); - - my $paging_nav = ''; - if ($page > 0) { - $paging_nav .= - $cgi->a({-href => href(action=>"search", hash=>$hash, - searchtext=>$searchtext, - searchtype=>$searchtype)}, - "first"); - $paging_nav .= " ⋅ " . - $cgi->a({-href => href(-replay=>1, page=>$page-1), - -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - $paging_nav .= "first"; - $paging_nav .= " ⋅ prev"; - } - my $next_link = ''; - if ($#commitlist >= 100) { - $next_link = - $cgi->a({-href => href(-replay=>1, page=>$page+1), - -accesskey => "n", -title => "Alt-n"}, "next"); - $paging_nav .= " ⋅ $next_link"; - } else { - $paging_nav .= " ⋅ next"; - } - - git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav); - git_print_header_div('commit', esc_html($co{'title'}), $hash); - if ($page == 0 && !@commitlist) { - print "<p>No match.</p>\n"; - } else { - git_search_grep_body(\@commitlist, 0, 99, $next_link); - } - } - - if ($searchtype eq 'pickaxe') { - git_print_page_nav('','', $hash,$co{'tree'},$hash); - git_print_header_div('commit', esc_html($co{'title'}), $hash); - - print "<table class=\"pickaxe search\">\n"; - my $alternate = 1; - local $/ = "\n"; - open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts, - '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext", - ($search_use_regexp ? '--pickaxe-regex' : ()); - undef %co; - my @files; - while (my $line = <$fd>) { - chomp $line; - next unless $line; - - my %set = parse_difftree_raw_line($line); - if (defined $set{'commit'}) { - # finish previous commit - if (%co) { - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . - " | " . - $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); - print "</td>\n" . - "</tr>\n"; - } - - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - %co = parse_commit($set{'commit'}); - my $author = chop_and_escape_str($co{'author_name'}, 15, 5); - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>$author</i></td>\n" . - "<td>" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), - -class => "list subject"}, - chop_and_escape_str($co{'title'}, 50) . "<br/>"); - } elsif (defined $set{'to_id'}) { - next if ($set{'to_id'} =~ m/^0{40}$/); - - print $cgi->a({-href => href(action=>"blob", hash_base=>$co{'id'}, - hash=>$set{'to_id'}, file_name=>$set{'to_file'}), - -class => "list"}, - "<span class=\"match\">" . esc_path($set{'file'}) . "</span>") . - "<br/>\n"; - } - } - close $fd; - - # finish last commit (warning: repetition!) - if (%co) { - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") . - " | " . - $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree"); - print "</td>\n" . - "</tr>\n"; - } - - print "</table>\n"; - } - - if ($searchtype eq 'grep') { - git_print_page_nav('','', $hash,$co{'tree'},$hash); - git_print_header_div('commit', esc_html($co{'title'}), $hash); - - print "<table class=\"grep_search\">\n"; - my $alternate = 1; - my $matches = 0; - local $/ = "\n"; - open my $fd, "-|", git_cmd(), 'grep', '-n', - $search_use_regexp ? ('-E', '-i') : '-F', - $searchtext, $co{'tree'}; - my $lastfile = ''; - while (my $line = <$fd>) { - chomp $line; - my ($file, $lno, $ltext, $binary); - last if ($matches++ > 1000); - if ($line =~ /^Binary file (.+) matches$/) { - $file = $1; - $binary = 1; - } else { - (undef, $file, $lno, $ltext) = split(/:/, $line, 4); - } - if ($file ne $lastfile) { - $lastfile and print "</td></tr>\n"; - if ($alternate++) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - print "<td class=\"list\">". - $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'}, - file_name=>"$file"), - -class => "list"}, esc_path($file)); - print "</td><td>\n"; - $lastfile = $file; - } - if ($binary) { - print "<div class=\"binary\">Binary file</div>\n"; - } else { - $ltext = untabify($ltext); - if ($ltext =~ m/^(.*)($search_regexp)(.*)$/i) { - $ltext = esc_html($1, -nbsp=>1); - $ltext .= '<span class="match">'; - $ltext .= esc_html($2, -nbsp=>1); - $ltext .= '</span>'; - $ltext .= esc_html($3, -nbsp=>1); - } else { - $ltext = esc_html($ltext, -nbsp=>1); - } - print "<div class=\"pre\">" . - $cgi->a({-href => href(action=>"blob", hash=>$co{'hash'}, - file_name=>"$file").'#l'.$lno, - -class => "linenr"}, sprintf('%4i', $lno)) - . ' ' . $ltext . "</div>\n"; - } - } - if ($lastfile) { - print "</td></tr>\n"; - if ($matches > 1000) { - print "<div class=\"diff nodifferences\">Too many matches, listing trimmed</div>\n"; - } - } else { - print "<div class=\"diff nodifferences\">No matches found</div>\n"; - } - close $fd; - - print "</table>\n"; + if ($searchtype eq 'commit' || + $searchtype eq 'author' || + $searchtype eq 'committer') { + git_search_message(%co); + } elsif ($searchtype eq 'pickaxe') { + git_search_changes(%co); + } elsif ($searchtype eq 'grep') { + git_search_files(%co); + } else { + die_error(400, "Unknown search type"); } - git_footer_html(); } sub git_search_help { diff --git a/gitweb/static/js/blame_incremental.js b/gitweb/static/js/blame_incremental.js index 676da6b59..db6eb5058 100644 --- a/gitweb/static/js/blame_incremental.js +++ b/gitweb/static/js/blame_incremental.js @@ -29,7 +29,6 @@ /* ............................................................ */ /* utility/helper functions (and variables) */ -var xhr; // XMLHttpRequest object var projectUrl; // partial query + separator ('?' or ';') // 'commits' is an associative map. It maps SHA1s to Commit objects. @@ -420,8 +419,6 @@ function handleLine(commit, group) { // ---------------------------------------------------------------------- -var inProgress = false; // are we processing response - /**#@+ * @constant */ @@ -433,8 +430,6 @@ var endRe = /^END ?([^ ]*) ?(.*)/; var curCommit = new Commit(); var curGroup = {}; -var pollTimer = null; - /** * Parse output from 'git blame --incremental [...]', received via * XMLHttpRequest from server (blamedataUrl), and call handleLine @@ -535,43 +530,51 @@ function processData(unprocessed, nextReadPos) { * Handle XMLHttpRequest errors * * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * @param {Number} [xhr.pollTimer] ID of the timeout to clear * - * @globals pollTimer, commits, inProgress + * @globals commits */ function handleError(xhr) { errorInfo('Server error: ' + xhr.status + ' - ' + (xhr.statusText || 'Error contacting server')); - clearInterval(pollTimer); + if (typeof xhr.pollTimer === "number") { + clearTimeout(xhr.pollTimer); + delete xhr.pollTimer; + } commits = {}; // free memory - - inProgress = false; } /** * Called after XMLHttpRequest finishes (loads) * - * @param {XMLHttpRequest} xhr: XMLHttpRequest object (unused) + * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * @param {Number} [xhr.pollTimer] ID of the timeout to clear * - * @globals pollTimer, commits, inProgress + * @globals commits */ function responseLoaded(xhr) { - clearInterval(pollTimer); + if (typeof xhr.pollTimer === "number") { + clearTimeout(xhr.pollTimer); + delete xhr.pollTimer; + } fixColorsAndGroups(); writeTimeInterval(); commits = {}; // free memory - - inProgress = false; } /** * handler for XMLHttpRequest onreadystatechange event * @see startBlame * - * @globals xhr, inProgress + * @param {XMLHttpRequest} xhr: XMLHttpRequest object + * @param {Number} xhr.prevDataLength: previous value of xhr.responseText.length + * @param {Number} xhr.nextReadPos: start of unread part of xhr.responseText + * @param {Number} [xhr.pollTimer] ID of the timeout (to reset or cancel) + * @param {Boolean} fromTimer: if handler was called from timer */ -function handleResponse() { +function handleResponse(xhr, fromTimer) { /* * xhr.readyState @@ -609,32 +612,31 @@ function handleResponse() { return; } - // in case we were called before finished processing - if (inProgress) { - return; - } else { - inProgress = true; - } // extract new whole (complete) lines, and process them - while (xhr.prevDataLength !== xhr.responseText.length) { - if (xhr.readyState === 4 && - xhr.prevDataLength === xhr.responseText.length) { - break; - } - + if (xhr.prevDataLength !== xhr.responseText.length) { xhr.prevDataLength = xhr.responseText.length; var unprocessed = xhr.responseText.substring(xhr.nextReadPos); xhr.nextReadPos = processData(unprocessed, xhr.nextReadPos); - } // end while + } // did we finish work? - if (xhr.readyState === 4 && - xhr.prevDataLength === xhr.responseText.length) { + if (xhr.readyState === 4) { responseLoaded(xhr); + return; } - inProgress = false; + // if we get from timer, we have to restart it + // otherwise onreadystatechange gives us partial response, timer not needed + if (fromTimer) { + setTimeout(function () { + handleResponse(xhr, true); + }, 1000); + + } else if (typeof xhr.pollTimer === "number") { + clearTimeout(xhr.pollTimer); + delete xhr.pollTimer; + } } // ============================================================ @@ -649,11 +651,11 @@ function handleResponse() { * Called from 'blame_incremental' view after loading table with * file contents, a base for blame view. * - * @globals xhr, t0, projectUrl, div_progress_bar, totalLines, pollTimer + * @globals t0, projectUrl, div_progress_bar, totalLines */ function startBlame(blamedataUrl, bUrl) { - xhr = createRequestObject(); + var xhr = createRequestObject(); if (!xhr) { errorInfo('ERROR: XMLHttpRequest not supported'); return; @@ -672,8 +674,9 @@ function startBlame(blamedataUrl, bUrl) { xhr.prevDataLength = -1; // used to detect if we have new data xhr.nextReadPos = 0; // where unread part of response starts - xhr.onreadystatechange = handleResponse; - //xhr.onreadystatechange = function () { handleResponse(xhr); }; + xhr.onreadystatechange = function () { + handleResponse(xhr, false); + }; xhr.open('GET', blamedataUrl); xhr.setRequestHeader('Accept', 'text/plain'); @@ -681,7 +684,9 @@ function startBlame(blamedataUrl, bUrl) { // not all browsers call onreadystatechange event on each server flush // poll response using timer every second to handle this issue - pollTimer = setInterval(xhr.onreadystatechange, 1000); + xhr.pollTimer = setTimeout(function () { + handleResponse(xhr, true); + }, 1000); } /* end of blame_incremental.js */ @@ -721,7 +721,10 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, int rest = eol - bol; char *line_color = NULL; - if (opt->pre_context || opt->post_context) { + if (opt->file_break && opt->last_shown == 0) { + if (opt->show_hunk_mark) + opt->output(opt, "\n", 1); + } else if (opt->pre_context || opt->post_context) { if (opt->last_shown == 0) { if (opt->show_hunk_mark) { output_color(opt, "--", 2, opt->color_sep); @@ -732,9 +735,13 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol, opt->output(opt, "\n", 1); } } + if (opt->heading && opt->last_shown == 0) { + output_color(opt, name, strlen(name), opt->color_filename); + opt->output(opt, "\n", 1); + } opt->last_shown = lno; - if (opt->pathname) { + if (!opt->heading && opt->pathname) { output_color(opt, name, strlen(name), opt->color_filename); output_sep(opt, sign); } @@ -941,9 +948,18 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name, if (!opt->output) opt->output = std_output; - if (opt->last_shown && (opt->pre_context || opt->post_context) && - opt->output == std_output) - opt->show_hunk_mark = 1; + if (opt->pre_context || opt->post_context || opt->file_break) { + /* Show hunk marks, except for the first file. */ + if (opt->last_shown) + opt->show_hunk_mark = 1; + /* + * If we're using threads then we can't easily identify + * the first file. Always put hunk marks in that case + * and skip the very first one later in work_done(). + */ + if (opt->output != std_output) + opt->show_hunk_mark = 1; + } opt->last_shown = 0; switch (opt->binary) { @@ -110,6 +110,8 @@ struct grep_opt { unsigned post_context; unsigned last_shown; int show_hunk_mark; + int file_break; + int heading; void *priv; void (*output)(struct grep_opt *opt, const void *data, size_t size); @@ -127,7 +127,10 @@ static int is_executable(const char *name) !S_ISREG(st.st_mode)) return 0; -#ifdef WIN32 +#if defined(WIN32) || defined(__CYGWIN__) +#if defined(__CYGWIN__) +if ((st.st_mode & S_IXUSR) == 0) +#endif { /* cannot trust the executable bit, peek into the file instead */ char buf[3] = { 0 }; int n; @@ -302,6 +305,10 @@ static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old) #define SIMILARITY_FLOOR 7 #define SIMILAR_ENOUGH(x) ((x) < SIMILARITY_FLOOR) +static const char bad_interpreter_advice[] = + N_("'%s' appears to be a git command, but we were not\n" + "able to execute it. Maybe git-%s is broken?"); + const char *help_unknown_cmd(const char *cmd) { int i, n, best_similarity = 0; @@ -326,6 +333,14 @@ const char *help_unknown_cmd(const char *cmd) int cmp = 0; /* avoid compiler stupidity */ const char *candidate = main_cmds.names[i]->name; + /* + * An exact match means we have the command, but + * for some reason exec'ing it gave us ENOENT; probably + * it's a bad interpreter in the #! line. + */ + if (!strcmp(candidate, cmd)) + die(_(bad_interpreter_advice), cmd, cmd); + /* Does the candidate appear in common_cmds list? */ while (n < ARRAY_SIZE(common_cmds) && (cmp = strcmp(common_cmds[n].name, candidate)) < 0) diff --git a/http-backend.c b/http-backend.c index 85015048d..59ad7da60 100644 --- a/http-backend.c +++ b/http-backend.c @@ -271,16 +271,13 @@ static struct rpc_service *select_service(const char *name) static void inflate_request(const char *prog_name, int out) { - z_stream stream; + git_zstream stream; unsigned char in_buf[8192]; unsigned char out_buf[8192]; unsigned long cnt = 0; - int ret; memset(&stream, 0, sizeof(stream)); - ret = inflateInit2(&stream, (15 + 16)); - if (ret != Z_OK) - die("cannot start zlib inflater, zlib err %d", ret); + git_inflate_init_gzip_only(&stream); while (1) { ssize_t n = xread(0, in_buf, sizeof(in_buf)); @@ -296,7 +293,7 @@ static void inflate_request(const char *prog_name, int out) stream.next_out = out_buf; stream.avail_out = sizeof(out_buf); - ret = inflate(&stream, Z_NO_FLUSH); + ret = git_inflate(&stream, Z_NO_FLUSH); if (ret != Z_OK && ret != Z_STREAM_END) die("zlib error inflating request, result %d", ret); @@ -311,7 +308,7 @@ static void inflate_request(const char *prog_name, int out) } done: - inflateEnd(&stream); + git_inflate_end(&stream); close(out); } diff --git a/http-push.c b/http-push.c index 28bfe768f..6e8f6d09a 100644 --- a/http-push.c +++ b/http-push.c @@ -377,15 +377,15 @@ static void start_put(struct transfer_request *request) unsigned long len; int hdrlen; ssize_t size; - z_stream stream; + git_zstream stream; unpacked = read_sha1_file(request->obj->sha1, &type, &len); hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1; /* Set it up */ memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); - size = deflateBound(&stream, len + hdrlen); + git_deflate_init(&stream, zlib_compression_level); + size = git_deflate_bound(&stream, len + hdrlen); strbuf_init(&request->buffer.buf, size); request->buffer.posn = 0; @@ -396,15 +396,15 @@ static void start_put(struct transfer_request *request) /* First header.. */ stream.next_in = (void *)hdr; stream.avail_in = hdrlen; - while (deflate(&stream, 0) == Z_OK) - /* nothing */; + while (git_deflate(&stream, 0) == Z_OK) + ; /* nothing */ /* Then the data itself.. */ stream.next_in = unpacked; stream.avail_in = len; - while (deflate(&stream, Z_FINISH) == Z_OK) - /* nothing */; - deflateEnd(&stream); + while (git_deflate(&stream, Z_FINISH) == Z_OK) + ; /* nothing */ + git_deflate_end(&stream); free(unpacked); request->buffer.buf.len = stream.total_out; @@ -41,6 +41,7 @@ static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; static int curl_ftp_no_epsv; static const char *curl_http_proxy; +static const char *curl_cookie_file; static char *user_name, *user_pass; static const char *user_agent; @@ -191,6 +192,9 @@ static int http_options(const char *var, const char *value, void *cb) if (!strcmp("http.proxy", var)) return git_config_string(&curl_http_proxy, var, value); + if (!strcmp("http.cookiefile", var)) + return git_config_string(&curl_cookie_file, var, value); + if (!strcmp("http.postbuffer", var)) { http_post_buffer = git_config_int(var, value); if (http_post_buffer < LARGE_PACKET_MAX) @@ -531,6 +535,7 @@ struct active_request_slot *get_active_slot(void) slot->finished = NULL; slot->callback_data = NULL; slot->callback_func = NULL; + curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL); @@ -172,7 +172,7 @@ struct http_object_request { unsigned char sha1[20]; unsigned char real_sha1[20]; git_SHA_CTX c; - z_stream stream; + git_zstream stream; int zret; int rename; struct active_request_slot *slot; diff --git a/pack-check.c b/pack-check.c index a1a521648..0c19b6e5a 100644 --- a/pack-check.c +++ b/pack-check.c @@ -26,7 +26,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, uint32_t data_crc = crc32(0, NULL, 0); do { - unsigned int avail; + unsigned long avail; void *data = use_pack(p, w_curs, offset, &avail); if (avail > len) avail = len; @@ -61,7 +61,7 @@ static int verify_packfile(struct packed_git *p, git_SHA1_Init(&ctx); do { - unsigned int remaining; + unsigned long remaining; unsigned char *in = use_pack(p, w_curs, offset, &remaining); offset += remaining; if (!pack_sig_ofs) diff --git a/pack-write.c b/pack-write.c index a905ca448..9cd3bfbb4 100644 --- a/pack-write.c +++ b/pack-write.c @@ -2,8 +2,12 @@ #include "pack.h" #include "csum-file.h" -uint32_t pack_idx_default_version = 2; -uint32_t pack_idx_off32_limit = 0x7fffffff; +void reset_pack_idx_option(struct pack_idx_option *opts) +{ + memset(opts, 0, sizeof(*opts)); + opts->version = 2; + opts->off32_limit = 0x7fffffff; +} static int sha1_compare(const void *_a, const void *_b) { @@ -12,13 +16,35 @@ static int sha1_compare(const void *_a, const void *_b) return hashcmp(a->sha1, b->sha1); } +static int cmp_uint32(const void *a_, const void *b_) +{ + uint32_t a = *((uint32_t *)a_); + uint32_t b = *((uint32_t *)b_); + + return (a < b) ? -1 : (a != b); +} + +static int need_large_offset(off_t offset, const struct pack_idx_option *opts) +{ + uint32_t ofsval; + + if ((offset >> 31) || (opts->off32_limit < offset)) + return 1; + if (!opts->anomaly_nr) + return 0; + ofsval = offset; + return !!bsearch(&ofsval, opts->anomaly, opts->anomaly_nr, + sizeof(ofsval), cmp_uint32); +} + /* * On entry *sha1 contains the pack content SHA1 hash, on exit it is * the SHA1 hash of sorted object names. The objects array passed in * will be sorted by SHA1 on exit. */ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, - int nr_objects, unsigned char *sha1) + int nr_objects, const struct pack_idx_option *opts, + unsigned char *sha1) { struct sha1file *f; struct pack_idx_entry **sorted_by_sha, **list, **last; @@ -42,20 +68,25 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec else sorted_by_sha = list = last = NULL; - if (!index_name) { - static char tmpfile[PATH_MAX]; - fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX"); - index_name = xstrdup(tmpfile); + if (opts->flags & WRITE_IDX_VERIFY) { + assert(index_name); + f = sha1fd_check(index_name); } else { - unlink(index_name); - fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600); + if (!index_name) { + static char tmpfile[PATH_MAX]; + fd = odb_mkstemp(tmpfile, sizeof(tmpfile), "pack/tmp_idx_XXXXXX"); + index_name = xstrdup(tmpfile); + } else { + unlink(index_name); + fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600); + } + if (fd < 0) + die_errno("unable to create '%s'", index_name); + f = sha1fd(fd, index_name); } - if (fd < 0) - die_errno("unable to create '%s'", index_name); - f = sha1fd(fd, index_name); /* if last object's offset is >= 2^31 we should use index V2 */ - index_version = (last_obj_offset >> 31) ? 2 : pack_idx_default_version; + index_version = need_large_offset(last_obj_offset, opts) ? 2 : opts->version; /* index versions 2 and above need a header */ if (index_version >= 2) { @@ -115,8 +146,11 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec list = sorted_by_sha; for (i = 0; i < nr_objects; i++) { struct pack_idx_entry *obj = *list++; - uint32_t offset = (obj->offset <= pack_idx_off32_limit) ? - obj->offset : (0x80000000 | nr_large_offset++); + uint32_t offset; + + offset = (need_large_offset(obj->offset, opts) + ? (0x80000000 | nr_large_offset++) + : obj->offset); offset = htonl(offset); sha1write(f, &offset, 4); } @@ -126,18 +160,20 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec while (nr_large_offset) { struct pack_idx_entry *obj = *list++; uint64_t offset = obj->offset; - if (offset > pack_idx_off32_limit) { - uint32_t split[2]; - split[0] = htonl(offset >> 32); - split[1] = htonl(offset & 0xffffffff); - sha1write(f, split, 8); - nr_large_offset--; - } + uint32_t split[2]; + + if (!need_large_offset(offset, opts)) + continue; + split[0] = htonl(offset >> 32); + split[1] = htonl(offset & 0xffffffff); + sha1write(f, split, 8); + nr_large_offset--; } } sha1write(f, sha1, 20); - sha1close(f, NULL, CSUM_FSYNC); + sha1close(f, NULL, ((opts->flags & WRITE_IDX_VERIFY) + ? CSUM_CLOSE : CSUM_FSYNC)); git_SHA1_Final(sha1, &ctx); return index_name; } @@ -34,9 +34,24 @@ struct pack_header { */ #define PACK_IDX_SIGNATURE 0xff744f63 /* "\377tOc" */ -/* These may be overridden by command-line parameters */ -extern uint32_t pack_idx_default_version; -extern uint32_t pack_idx_off32_limit; +struct pack_idx_option { + unsigned flags; + /* flag bits */ +#define WRITE_IDX_VERIFY 01 + + uint32_t version; + uint32_t off32_limit; + + /* + * List of offsets that would fit within off32_limit but + * need to be written out as 64-bit entity for byte-for-byte + * verification. + */ + int anomaly_alloc, anomaly_nr; + uint32_t *anomaly; +}; + +extern void reset_pack_idx_option(struct pack_idx_option *); /* * Packed object index header @@ -55,7 +70,7 @@ struct pack_idx_entry { off_t offset; }; -extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1); +extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, const struct pack_idx_option *, unsigned char *sha1); extern int check_pack_crc(struct packed_git *p, struct pack_window **w_curs, off_t offset, off_t len, unsigned int nr); extern int verify_pack_index(struct packed_git *); extern int verify_pack(struct packed_git *); diff --git a/parse-options.c b/parse-options.c index 73bd28ad9..879ea82a3 100644 --- a/parse-options.c +++ b/parse-options.c @@ -3,6 +3,7 @@ #include "cache.h" #include "commit.h" #include "color.h" +#include "string-list.h" static int parse_options_usage(struct parse_opt_ctx_t *ctx, const char * const *usagestr, @@ -687,3 +688,19 @@ int parse_options_concat(struct option *dst, size_t dst_size, struct option *src } return -1; } + +int parse_opt_string_list(const struct option *opt, const char *arg, int unset) +{ + struct string_list *v = opt->value; + + if (unset) { + string_list_clear(v, 0); + return 0; + } + + if (!arg) + return -1; + + string_list_append(v, xstrdup(arg)); + return 0; +} diff --git a/parse-options.h b/parse-options.h index d1b12fe97..05eb09b87 100644 --- a/parse-options.h +++ b/parse-options.h @@ -130,6 +130,9 @@ struct option { (h), PARSE_OPT_NOARG, NULL, (p) } #define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } +#define OPT_STRING_LIST(s, l, v, a, h) \ + { OPTION_CALLBACK, (s), (l), (v), (a), \ + (h), 0, &parse_opt_string_list } #define OPT_UYN(s, l, v, h) { OPTION_CALLBACK, (s), (l), (v), NULL, \ (h), PARSE_OPT_NOARG, &parse_opt_tertiary } #define OPT_DATE(s, l, v, h) \ @@ -204,6 +207,7 @@ extern int parse_opt_color_flag_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); extern int parse_opt_with_commit(const struct option *, const char *, int); extern int parse_opt_tertiary(const struct option *, const char *, int); +extern int parse_opt_string_list(const struct option *, const char *, int); #define OPT__VERBOSE(var, h) OPT_BOOLEAN('v', "verbose", (var), (h)) #define OPT__QUIET(var, h) OPT_BOOLEAN('q', "quiet", (var), (h)) diff --git a/read-cache.c b/read-cache.c index 4ac9a037f..46a9e6070 100644 --- a/read-cache.c +++ b/read-cache.c @@ -726,11 +726,12 @@ static int verify_dotfile(const char *rest) * has already been discarded, we now test * the rest. */ - switch (*rest) { + /* "." is not allowed */ - case '\0': case '/': + if (*rest == '\0' || is_dir_sep(*rest)) return 0; + switch (*rest) { /* * ".git" followed by NUL or slash is bad. This * shares the path end test with the ".." case. @@ -743,7 +744,7 @@ static int verify_dotfile(const char *rest) rest += 2; /* fallthrough */ case '.': - if (rest[1] == '\0' || rest[1] == '/') + if (rest[1] == '\0' || is_dir_sep(rest[1])) return 0; } return 1; @@ -753,23 +754,19 @@ int verify_path(const char *path) { char c; + if (has_dos_drive_prefix(path)) + return 0; + goto inside; for (;;) { if (!c) return 1; - if (c == '/') { + if (is_dir_sep(c)) { inside: c = *path++; - switch (c) { - default: - continue; - case '/': case '\0': - break; - case '.': - if (verify_dotfile(path)) - continue; - } - return 0; + if ((c == '.' && !verify_dotfile(path)) || + is_dir_sep(c) || c == '\0') + return 0; } c = *path++; } @@ -1826,6 +1826,12 @@ int update_ref(const char *action, const char *refname, return 0; } +int ref_exists(char *refname) +{ + unsigned char sha1[20]; + return !!resolve_ref(refname, sha1, 1, NULL); +} + struct ref *find_ref_by_name(const struct ref *list, const char *name) { for ( ; list; list = list->next) @@ -54,6 +54,7 @@ extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refn */ extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags); extern void clear_extra_refs(void); +extern int ref_exists(char *); extern int peel_ref(const char *, unsigned char *); diff --git a/remote-curl.c b/remote-curl.c index 8ac502834..b8cf45a7d 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -473,16 +473,12 @@ static int post_rpc(struct rpc_state *rpc) * the transfer time. */ size_t size; - z_stream stream; + git_zstream stream; int ret; memset(&stream, 0, sizeof(stream)); - ret = deflateInit2(&stream, Z_BEST_COMPRESSION, - Z_DEFLATED, (15 + 16), - 8, Z_DEFAULT_STRATEGY); - if (ret != Z_OK) - die("cannot deflate request; zlib init error %d", ret); - size = deflateBound(&stream, rpc->len); + git_deflate_init_gzip(&stream, Z_BEST_COMPRESSION); + size = git_deflate_bound(&stream, rpc->len); gzip_body = xmalloc(size); stream.next_in = (unsigned char *)rpc->buf; @@ -490,11 +486,11 @@ static int post_rpc(struct rpc_state *rpc) stream.next_out = (unsigned char *)gzip_body; stream.avail_out = size; - ret = deflate(&stream, Z_FINISH); + ret = git_deflate(&stream, Z_FINISH); if (ret != Z_STREAM_END) die("cannot deflate request; zlib deflate error %d", ret); - ret = deflateEnd(&stream); + ret = git_deflate_end_gently(&stream); if (ret != Z_OK) die("cannot deflate request; zlib end error %d", ret); @@ -859,7 +855,14 @@ int main(int argc, const char **argv) http_init(remote); do { - if (strbuf_getline(&buf, stdin, '\n') == EOF) + if (strbuf_getline(&buf, stdin, '\n') == EOF) { + if (ferror(stdin)) + fprintf(stderr, "Error reading command stream\n"); + else + fprintf(stderr, "Unexpected end of command stream\n"); + return 1; + } + if (buf.len == 0) break; if (!prefixcmp(buf.buf, "fetch ")) { if (nongit) @@ -899,6 +902,7 @@ int main(int argc, const char **argv) printf("\n"); fflush(stdout); } else { + fprintf(stderr, "Unknown command '%s'\n", buf.buf); return 1; } strbuf_reset(&buf); @@ -896,7 +896,7 @@ struct ref *alloc_ref(const char *name) return alloc_ref_with_prefix("", 0, name); } -static struct ref *copy_ref(const struct ref *ref) +struct ref *copy_ref(const struct ref *ref) { struct ref *cpy; size_t len; @@ -1667,7 +1667,9 @@ struct ref *guess_remote_head(const struct ref *head, /* Look for another ref that points there */ for (r = refs; r; r = r->next) { - if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) { + if (r != head && + !prefixcmp(r->name, "refs/heads/") && + !hashcmp(r->old_sha1, head->old_sha1)) { *tail = copy_ref(r); tail = &((*tail)->next); if (!all) @@ -70,7 +70,7 @@ struct refspec { extern const struct refspec *tag_refspec; struct ref *alloc_ref(const char *name); - +struct ref *copy_ref(const struct ref *ref); struct ref *copy_ref_list(const struct ref *ref); int check_ref_type(const struct ref *ref, int flags); @@ -710,6 +710,11 @@ const char *setup_git_directory_gently(int *nongit_ok) const char *prefix; prefix = setup_git_directory_gently_1(nongit_ok); + if (prefix) + setenv("GIT_PREFIX", prefix, 1); + else + setenv("GIT_PREFIX", "", 1); + if (startup_info) { startup_info->have_repository = !nongit_ok || !*nongit_ok; startup_info->prefix = prefix; diff --git a/sh-i18n--envsubst.c b/sh-i18n--envsubst.c index 9d2e971a2..5ddd6886c 100644 --- a/sh-i18n--envsubst.c +++ b/sh-i18n--envsubst.c @@ -73,6 +73,7 @@ main (int argc, char *argv[]) { case 1: error ("we won't substitute all variables on stdin for you"); + break; /* all_variables = 1; subst_from_stdin (); diff --git a/sha1_file.c b/sha1_file.c index 064a33040..d5616dca0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -834,7 +834,7 @@ static int in_window(struct pack_window *win, off_t offset) unsigned char *use_pack(struct packed_git *p, struct pack_window **w_cursor, off_t offset, - unsigned int *left) + unsigned long *left) { struct pack_window *win = *w_cursor; @@ -1186,7 +1186,7 @@ static int open_sha1_file(const unsigned char *sha1) return -1; } -static void *map_sha1_file(const unsigned char *sha1, unsigned long *size) +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { void *map; int fd; @@ -1205,20 +1205,29 @@ static void *map_sha1_file(const unsigned char *sha1, unsigned long *size) return map; } -static int legacy_loose_object(unsigned char *map) +/* + * There used to be a second loose object header format which + * was meant to mimic the in-pack format, allowing for direct + * copy of the object data. This format turned up not to be + * really worth it and we no longer write loose objects in that + * format. + */ +static int experimental_loose_object(unsigned char *map) { unsigned int word; /* * Is it a zlib-compressed buffer? If so, the first byte * must be 0x78 (15-bit window size, deflated), and the - * first 16-bit word is evenly divisible by 31 + * first 16-bit word is evenly divisible by 31. If so, + * we are looking at the official format, not the experimental + * one. */ word = (map[0] << 8) + map[1]; if (map[0] == 0x78 && !(word % 31)) - return 1; - else return 0; + else + return 1; } unsigned long unpack_object_header_buffer(const unsigned char *buf, @@ -1245,7 +1254,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf, return used; } -static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) +int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) { unsigned long size, used; static const char valid_loose_object_type[8] = { @@ -1262,37 +1271,32 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon stream->next_out = buffer; stream->avail_out = bufsiz; - if (legacy_loose_object(map)) { - git_inflate_init(stream); - return git_inflate(stream, 0); - } - + if (experimental_loose_object(map)) { + /* + * The old experimental format we no longer produce; + * we can still read it. + */ + used = unpack_object_header_buffer(map, mapsize, &type, &size); + if (!used || !valid_loose_object_type[type]) + return -1; + map += used; + mapsize -= used; - /* - * There used to be a second loose object header format which - * was meant to mimic the in-pack format, allowing for direct - * copy of the object data. This format turned up not to be - * really worth it and we don't write it any longer. But we - * can still read it. - */ - used = unpack_object_header_buffer(map, mapsize, &type, &size); - if (!used || !valid_loose_object_type[type]) - return -1; - map += used; - mapsize -= used; + /* Set up the stream for the rest.. */ + stream->next_in = map; + stream->avail_in = mapsize; + git_inflate_init(stream); - /* Set up the stream for the rest.. */ - stream->next_in = map; - stream->avail_in = mapsize; + /* And generate the fake traditional header */ + stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", + typename(type), size); + return 0; + } git_inflate_init(stream); - - /* And generate the fake traditional header */ - stream->total_out = 1 + snprintf(buffer, bufsiz, "%s %lu", - typename(type), size); - return 0; + return git_inflate(stream, 0); } -static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1) +static void *unpack_sha1_rest(git_zstream *stream, void *buffer, unsigned long size, const unsigned char *sha1) { int bytes = strlen(buffer) + 1; unsigned char *buf = xmallocz(size); @@ -1342,7 +1346,7 @@ static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size * too permissive for what we want to check. So do an anal * object header parse by hand. */ -static int parse_sha1_header(const char *hdr, unsigned long *sizep) +int parse_sha1_header(const char *hdr, unsigned long *sizep) { char type[10]; int i; @@ -1391,7 +1395,7 @@ static int parse_sha1_header(const char *hdr, unsigned long *sizep) static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1) { int ret; - z_stream stream; + git_zstream stream; char hdr[8192]; ret = unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)); @@ -1407,7 +1411,7 @@ unsigned long get_size_from_delta(struct packed_git *p, { const unsigned char *data; unsigned char delta_head[20], *in; - z_stream stream; + git_zstream stream; int st; memset(&stream, 0, sizeof(stream)); @@ -1481,7 +1485,7 @@ static off_t get_delta_base(struct packed_git *p, /* forward declaration for a mutually recursive function */ static int packed_object_info(struct packed_git *p, off_t offset, - unsigned long *sizep); + unsigned long *sizep, int *rtype); static int packed_delta_info(struct packed_git *p, struct pack_window **w_curs, @@ -1495,7 +1499,7 @@ static int packed_delta_info(struct packed_git *p, base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset); if (!base_offset) return OBJ_BAD; - type = packed_object_info(p, base_offset, NULL); + type = packed_object_info(p, base_offset, NULL, NULL); if (type <= OBJ_NONE) { struct revindex_entry *revidx; const unsigned char *base_sha1; @@ -1523,13 +1527,13 @@ static int packed_delta_info(struct packed_git *p, return type; } -static int unpack_object_header(struct packed_git *p, - struct pack_window **w_curs, - off_t *curpos, - unsigned long *sizep) +int unpack_object_header(struct packed_git *p, + struct pack_window **w_curs, + off_t *curpos, + unsigned long *sizep) { unsigned char *base; - unsigned int left; + unsigned long left; unsigned long used; enum object_type type; @@ -1549,63 +1553,8 @@ static int unpack_object_header(struct packed_git *p, return type; } -const char *packed_object_info_detail(struct packed_git *p, - off_t obj_offset, - unsigned long *size, - unsigned long *store_size, - unsigned int *delta_chain_length, - unsigned char *base_sha1) -{ - struct pack_window *w_curs = NULL; - off_t curpos; - unsigned long dummy; - unsigned char *next_sha1; - enum object_type type; - struct revindex_entry *revidx; - - *delta_chain_length = 0; - curpos = obj_offset; - type = unpack_object_header(p, &w_curs, &curpos, size); - - revidx = find_pack_revindex(p, obj_offset); - *store_size = revidx[1].offset - obj_offset; - - for (;;) { - switch (type) { - default: - die("pack %s contains unknown object type %d", - p->pack_name, type); - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - unuse_pack(&w_curs); - return typename(type); - case OBJ_OFS_DELTA: - obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); - if (!obj_offset) - die("pack %s contains bad delta base reference of type %s", - p->pack_name, typename(type)); - if (*delta_chain_length == 0) { - revidx = find_pack_revindex(p, obj_offset); - hashcpy(base_sha1, nth_packed_object_sha1(p, revidx->nr)); - } - break; - case OBJ_REF_DELTA: - next_sha1 = use_pack(p, &w_curs, curpos, NULL); - if (*delta_chain_length == 0) - hashcpy(base_sha1, next_sha1); - obj_offset = find_pack_entry_one(next_sha1, p); - break; - } - (*delta_chain_length)++; - curpos = obj_offset; - type = unpack_object_header(p, &w_curs, &curpos, &dummy); - } -} - static int packed_object_info(struct packed_git *p, off_t obj_offset, - unsigned long *sizep) + unsigned long *sizep, int *rtype) { struct pack_window *w_curs = NULL; unsigned long size; @@ -1613,6 +1562,8 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, enum object_type type; type = unpack_object_header(p, &w_curs, &curpos, &size); + if (rtype) + *rtype = type; /* representation type */ switch (type) { case OBJ_OFS_DELTA: @@ -1642,7 +1593,7 @@ static void *unpack_compressed_entry(struct packed_git *p, unsigned long size) { int st; - z_stream stream; + git_zstream stream; unsigned char *buffer, *in; buffer = xmallocz(size); @@ -1695,6 +1646,13 @@ static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset) return hash % MAX_DELTA_CACHE; } +static int in_delta_base_cache(struct packed_git *p, off_t base_offset) +{ + unsigned long hash = pack_entry_hash(p, base_offset); + struct delta_base_cache_entry *ent = delta_base_cache + hash; + return (ent->data && ent->p == p && ent->base_offset == base_offset); +} + static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset, unsigned long *base_size, enum object_type *type, int keep_cache) { @@ -1839,6 +1797,24 @@ static void *unpack_delta_entry(struct packed_git *p, return result; } +static void write_pack_access_log(struct packed_git *p, off_t obj_offset) +{ + static FILE *log_file; + + if (!log_file) { + log_file = fopen(log_pack_access, "w"); + if (!log_file) { + error("cannot open pack access log '%s' for writing: %s", + log_pack_access, strerror(errno)); + log_pack_access = NULL; + return; + } + } + fprintf(log_file, "%s %"PRIuMAX"\n", + p->pack_name, (uintmax_t)obj_offset); + fflush(log_file); +} + int do_check_packed_object_crc; void *unpack_entry(struct packed_git *p, off_t obj_offset, @@ -1848,6 +1824,9 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, off_t curpos = obj_offset; void *data; + if (log_pack_access) + write_pack_access_log(p, obj_offset); + if (do_check_packed_object_crc && p->index_version > 1) { struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); unsigned long len = revidx[1].offset - obj_offset; @@ -2075,7 +2054,7 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size int status; unsigned long mapsize, size; void *map; - z_stream stream; + git_zstream stream; char hdr[32]; map = map_sha1_file(sha1, &mapsize); @@ -2093,24 +2072,28 @@ static int sha1_loose_object_info(const unsigned char *sha1, unsigned long *size return status; } -int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) +/* returns enum object_type or negative */ +int sha1_object_info_extended(const unsigned char *sha1, struct object_info *oi) { struct cached_object *co; struct pack_entry e; - int status; + int status, rtype; co = find_cached_object(sha1); if (co) { - if (sizep) - *sizep = co->size; + if (oi->sizep) + *(oi->sizep) = co->size; + oi->whence = OI_CACHED; return co->type; } if (!find_pack_entry(sha1, &e)) { /* Most likely it's a loose object. */ - status = sha1_loose_object_info(sha1, sizep); - if (status >= 0) + status = sha1_loose_object_info(sha1, oi->sizep); + if (status >= 0) { + oi->whence = OI_LOOSE; return status; + } /* Not a loose object; someone else may have just packed it. */ reprepare_packed_git(); @@ -2118,15 +2101,31 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) return status; } - status = packed_object_info(e.p, e.offset, sizep); + status = packed_object_info(e.p, e.offset, oi->sizep, &rtype); if (status < 0) { mark_bad_packed_object(e.p, sha1); - status = sha1_object_info(sha1, sizep); + status = sha1_object_info_extended(sha1, oi); + } else if (in_delta_base_cache(e.p, e.offset)) { + oi->whence = OI_DBCACHED; + } else { + oi->whence = OI_PACKED; + oi->u.packed.offset = e.offset; + oi->u.packed.pack = e.p; + oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA || + rtype == OBJ_OFS_DELTA); } return status; } +int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) +{ + struct object_info oi; + + oi.sizep = sizep; + return sha1_object_info_extended(sha1, &oi); +} + static void *read_packed_sha1(const unsigned char *sha1, enum object_type *type, unsigned long *size) { @@ -2424,7 +2423,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, { int fd, ret; unsigned char compressed[4096]; - z_stream stream; + git_zstream stream; git_SHA_CTX c; unsigned char parano_sha1[20]; char *filename; @@ -2441,7 +2440,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, /* Set it up */ memset(&stream, 0, sizeof(stream)); - deflateInit(&stream, zlib_compression_level); + git_deflate_init(&stream, zlib_compression_level); stream.next_out = compressed; stream.avail_out = sizeof(compressed); git_SHA1_Init(&c); @@ -2449,8 +2448,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, /* First header.. */ stream.next_in = (unsigned char *)hdr; stream.avail_in = hdrlen; - while (deflate(&stream, 0) == Z_OK) - /* nothing */; + while (git_deflate(&stream, 0) == Z_OK) + ; /* nothing */ git_SHA1_Update(&c, hdr, hdrlen); /* Then the data itself.. */ @@ -2458,7 +2457,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, stream.avail_in = len; do { unsigned char *in0 = stream.next_in; - ret = deflate(&stream, Z_FINISH); + ret = git_deflate(&stream, Z_FINISH); git_SHA1_Update(&c, in0, stream.next_in - in0); if (write_buffer(fd, compressed, stream.next_out - compressed) < 0) die("unable to write sha1 file"); @@ -2468,7 +2467,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, if (ret != Z_STREAM_END) die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret); - ret = deflateEnd(&stream); + ret = git_deflate_end_gently(&stream); if (ret != Z_OK) die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret); git_SHA1_Final(parano_sha1, &c); @@ -2704,7 +2703,7 @@ static int index_stream(unsigned char *sha1, int fd, size_t size, while (size) { char buf[10240]; size_t sz = size < sizeof(buf) ? size : sizeof(buf); - size_t actual; + ssize_t actual; actual = read_in_full(fd, buf, sz); if (actual < 0) @@ -103,24 +103,27 @@ void strbuf_ltrim(struct strbuf *sb) sb->buf[sb->len] = '\0'; } -struct strbuf **strbuf_split(const struct strbuf *sb, int delim) +struct strbuf **strbuf_split_buf(const char *str, size_t slen, int delim, int max) { int alloc = 2, pos = 0; - char *n, *p; + const char *n, *p; struct strbuf **ret; struct strbuf *t; ret = xcalloc(alloc, sizeof(struct strbuf *)); - p = n = sb->buf; - while (n < sb->buf + sb->len) { + p = n = str; + while (n < str + slen) { int len; - n = memchr(n, delim, sb->len - (n - sb->buf)); + if (max <= 0 || pos + 1 < max) + n = memchr(n, delim, slen - (n - str)); + else + n = NULL; if (pos + 1 >= alloc) { alloc = alloc * 2; ret = xrealloc(ret, sizeof(struct strbuf *) * alloc); } if (!n) - n = sb->buf + sb->len - 1; + n = str + slen - 1; len = n - p + 1; t = xmalloc(sizeof(struct strbuf)); strbuf_init(t, len); @@ -44,7 +44,22 @@ extern void strbuf_rtrim(struct strbuf *); extern void strbuf_ltrim(struct strbuf *); extern int strbuf_cmp(const struct strbuf *, const struct strbuf *); -extern struct strbuf **strbuf_split(const struct strbuf *, int delim); +extern struct strbuf **strbuf_split_buf(const char *, size_t, + int delim, int max); +static inline struct strbuf **strbuf_split_str(const char *str, + int delim, int max) +{ + return strbuf_split_buf(str, strlen(str), delim, max); +} +static inline struct strbuf **strbuf_split_max(const struct strbuf *sb, + int delim, int max) +{ + return strbuf_split_buf(sb->buf, sb->len, delim, max); +} +static inline struct strbuf **strbuf_split(const struct strbuf *sb, int delim) +{ + return strbuf_split_max(sb, delim, 0); +} extern void strbuf_list_free(struct strbuf **); /*----- add data in your buffer -----*/ diff --git a/streaming.c b/streaming.c new file mode 100644 index 000000000..71072e1b1 --- /dev/null +++ b/streaming.c @@ -0,0 +1,491 @@ +/* + * Copyright (c) 2011, Google Inc. + */ +#include "cache.h" +#include "streaming.h" + +enum input_source { + stream_error = -1, + incore = 0, + loose = 1, + pack_non_delta = 2 +}; + +typedef int (*open_istream_fn)(struct git_istream *, + struct object_info *, + const unsigned char *, + enum object_type *); +typedef int (*close_istream_fn)(struct git_istream *); +typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t); + +struct stream_vtbl { + close_istream_fn close; + read_istream_fn read; +}; + +#define open_method_decl(name) \ + int open_istream_ ##name \ + (struct git_istream *st, struct object_info *oi, \ + const unsigned char *sha1, \ + enum object_type *type) + +#define close_method_decl(name) \ + int close_istream_ ##name \ + (struct git_istream *st) + +#define read_method_decl(name) \ + ssize_t read_istream_ ##name \ + (struct git_istream *st, char *buf, size_t sz) + +/* forward declaration */ +static open_method_decl(incore); +static open_method_decl(loose); +static open_method_decl(pack_non_delta); +static struct git_istream *attach_stream_filter(struct git_istream *st, + struct stream_filter *filter); + + +static open_istream_fn open_istream_tbl[] = { + open_istream_incore, + open_istream_loose, + open_istream_pack_non_delta, +}; + +#define FILTER_BUFFER (1024*16) + +struct filtered_istream { + struct git_istream *upstream; + struct stream_filter *filter; + char ibuf[FILTER_BUFFER]; + char obuf[FILTER_BUFFER]; + int i_end, i_ptr; + int o_end, o_ptr; + int input_finished; +}; + +struct git_istream { + const struct stream_vtbl *vtbl; + unsigned long size; /* inflated size of full object */ + git_zstream z; + enum { z_unused, z_used, z_done, z_error } z_state; + + union { + struct { + char *buf; /* from read_object() */ + unsigned long read_ptr; + } incore; + + struct { + void *mapped; + unsigned long mapsize; + char hdr[32]; + int hdr_avail; + int hdr_used; + } loose; + + struct { + struct packed_git *pack; + off_t pos; + } in_pack; + + struct filtered_istream filtered; + } u; +}; + +int close_istream(struct git_istream *st) +{ + int r = st->vtbl->close(st); + free(st); + return r; +} + +ssize_t read_istream(struct git_istream *st, char *buf, size_t sz) +{ + return st->vtbl->read(st, buf, sz); +} + +static enum input_source istream_source(const unsigned char *sha1, + enum object_type *type, + struct object_info *oi) +{ + unsigned long size; + int status; + + oi->sizep = &size; + status = sha1_object_info_extended(sha1, oi); + if (status < 0) + return stream_error; + *type = status; + + switch (oi->whence) { + case OI_LOOSE: + return loose; + case OI_PACKED: + if (!oi->u.packed.is_delta && big_file_threshold <= size) + return pack_non_delta; + /* fallthru */ + default: + return incore; + } +} + +struct git_istream *open_istream(const unsigned char *sha1, + enum object_type *type, + unsigned long *size, + struct stream_filter *filter) +{ + struct git_istream *st; + struct object_info oi; + const unsigned char *real = lookup_replace_object(sha1); + enum input_source src = istream_source(real, type, &oi); + + if (src < 0) + return NULL; + + st = xmalloc(sizeof(*st)); + if (open_istream_tbl[src](st, &oi, real, type)) { + if (open_istream_incore(st, &oi, real, type)) { + free(st); + return NULL; + } + } + if (st && filter) { + /* Add "&& !is_null_stream_filter(filter)" for performance */ + struct git_istream *nst = attach_stream_filter(st, filter); + if (!nst) + close_istream(st); + st = nst; + } + + *size = st->size; + return st; +} + + +/***************************************************************** + * + * Common helpers + * + *****************************************************************/ + +static void close_deflated_stream(struct git_istream *st) +{ + if (st->z_state == z_used) + git_inflate_end(&st->z); +} + + +/***************************************************************** + * + * Filtered stream + * + *****************************************************************/ + +static close_method_decl(filtered) +{ + free_stream_filter(st->u.filtered.filter); + return close_istream(st->u.filtered.upstream); +} + +static read_method_decl(filtered) +{ + struct filtered_istream *fs = &(st->u.filtered); + size_t filled = 0; + + while (sz) { + /* do we already have filtered output? */ + if (fs->o_ptr < fs->o_end) { + size_t to_move = fs->o_end - fs->o_ptr; + if (sz < to_move) + to_move = sz; + memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move); + fs->o_ptr += to_move; + sz -= to_move; + filled += to_move; + continue; + } + fs->o_end = fs->o_ptr = 0; + + /* do we have anything to feed the filter with? */ + if (fs->i_ptr < fs->i_end) { + size_t to_feed = fs->i_end - fs->i_ptr; + size_t to_receive = FILTER_BUFFER; + if (stream_filter(fs->filter, + fs->ibuf + fs->i_ptr, &to_feed, + fs->obuf, &to_receive)) + return -1; + fs->i_ptr = fs->i_end - to_feed; + fs->o_end = FILTER_BUFFER - to_receive; + continue; + } + + /* tell the filter to drain upon no more input */ + if (fs->input_finished) { + size_t to_receive = FILTER_BUFFER; + if (stream_filter(fs->filter, + NULL, NULL, + fs->obuf, &to_receive)) + return -1; + fs->o_end = FILTER_BUFFER - to_receive; + if (!fs->o_end) + break; + continue; + } + fs->i_end = fs->i_ptr = 0; + + /* refill the input from the upstream */ + if (!fs->input_finished) { + fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER); + if (fs->i_end < 0) + break; + if (fs->i_end) + continue; + } + fs->input_finished = 1; + } + return filled; +} + +static struct stream_vtbl filtered_vtbl = { + close_istream_filtered, + read_istream_filtered, +}; + +static struct git_istream *attach_stream_filter(struct git_istream *st, + struct stream_filter *filter) +{ + struct git_istream *ifs = xmalloc(sizeof(*ifs)); + struct filtered_istream *fs = &(ifs->u.filtered); + + ifs->vtbl = &filtered_vtbl; + fs->upstream = st; + fs->filter = filter; + fs->i_end = fs->i_ptr = 0; + fs->o_end = fs->o_ptr = 0; + fs->input_finished = 0; + ifs->size = -1; /* unknown */ + return ifs; +} + +/***************************************************************** + * + * Loose object stream + * + *****************************************************************/ + +static read_method_decl(loose) +{ + size_t total_read = 0; + + switch (st->z_state) { + case z_done: + return 0; + case z_error: + return -1; + default: + break; + } + + if (st->u.loose.hdr_used < st->u.loose.hdr_avail) { + size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used; + if (sz < to_copy) + to_copy = sz; + memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy); + st->u.loose.hdr_used += to_copy; + total_read += to_copy; + } + + while (total_read < sz) { + int status; + + st->z.next_out = (unsigned char *)buf + total_read; + st->z.avail_out = sz - total_read; + status = git_inflate(&st->z, Z_FINISH); + + total_read = st->z.next_out - (unsigned char *)buf; + + if (status == Z_STREAM_END) { + git_inflate_end(&st->z); + st->z_state = z_done; + break; + } + if (status != Z_OK && status != Z_BUF_ERROR) { + git_inflate_end(&st->z); + st->z_state = z_error; + return -1; + } + } + return total_read; +} + +static close_method_decl(loose) +{ + close_deflated_stream(st); + munmap(st->u.loose.mapped, st->u.loose.mapsize); + return 0; +} + +static struct stream_vtbl loose_vtbl = { + close_istream_loose, + read_istream_loose, +}; + +static open_method_decl(loose) +{ + st->u.loose.mapped = map_sha1_file(sha1, &st->u.loose.mapsize); + if (!st->u.loose.mapped) + return -1; + if (unpack_sha1_header(&st->z, + st->u.loose.mapped, + st->u.loose.mapsize, + st->u.loose.hdr, + sizeof(st->u.loose.hdr)) < 0) { + git_inflate_end(&st->z); + munmap(st->u.loose.mapped, st->u.loose.mapsize); + return -1; + } + + parse_sha1_header(st->u.loose.hdr, &st->size); + st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1; + st->u.loose.hdr_avail = st->z.total_out; + st->z_state = z_used; + + st->vtbl = &loose_vtbl; + return 0; +} + + +/***************************************************************** + * + * Non-delta packed object stream + * + *****************************************************************/ + +static read_method_decl(pack_non_delta) +{ + size_t total_read = 0; + + switch (st->z_state) { + case z_unused: + memset(&st->z, 0, sizeof(st->z)); + git_inflate_init(&st->z); + st->z_state = z_used; + break; + case z_done: + return 0; + case z_error: + return -1; + case z_used: + break; + } + + while (total_read < sz) { + int status; + struct pack_window *window = NULL; + unsigned char *mapped; + + mapped = use_pack(st->u.in_pack.pack, &window, + st->u.in_pack.pos, &st->z.avail_in); + + st->z.next_out = (unsigned char *)buf + total_read; + st->z.avail_out = sz - total_read; + st->z.next_in = mapped; + status = git_inflate(&st->z, Z_FINISH); + + st->u.in_pack.pos += st->z.next_in - mapped; + total_read = st->z.next_out - (unsigned char *)buf; + unuse_pack(&window); + + if (status == Z_STREAM_END) { + git_inflate_end(&st->z); + st->z_state = z_done; + break; + } + if (status != Z_OK && status != Z_BUF_ERROR) { + git_inflate_end(&st->z); + st->z_state = z_error; + return -1; + } + } + return total_read; +} + +static close_method_decl(pack_non_delta) +{ + close_deflated_stream(st); + return 0; +} + +static struct stream_vtbl pack_non_delta_vtbl = { + close_istream_pack_non_delta, + read_istream_pack_non_delta, +}; + +static open_method_decl(pack_non_delta) +{ + struct pack_window *window; + enum object_type in_pack_type; + + st->u.in_pack.pack = oi->u.packed.pack; + st->u.in_pack.pos = oi->u.packed.offset; + window = NULL; + + in_pack_type = unpack_object_header(st->u.in_pack.pack, + &window, + &st->u.in_pack.pos, + &st->size); + unuse_pack(&window); + switch (in_pack_type) { + default: + return -1; /* we do not do deltas for now */ + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + break; + } + st->z_state = z_unused; + st->vtbl = &pack_non_delta_vtbl; + return 0; +} + + +/***************************************************************** + * + * In-core stream + * + *****************************************************************/ + +static close_method_decl(incore) +{ + free(st->u.incore.buf); + return 0; +} + +static read_method_decl(incore) +{ + size_t read_size = sz; + size_t remainder = st->size - st->u.incore.read_ptr; + + if (remainder <= read_size) + read_size = remainder; + if (read_size) { + memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size); + st->u.incore.read_ptr += read_size; + } + return read_size; +} + +static struct stream_vtbl incore_vtbl = { + close_istream_incore, + read_istream_incore, +}; + +static open_method_decl(incore) +{ + st->u.incore.buf = read_sha1_file_extended(sha1, type, &st->size, 0); + st->u.incore.read_ptr = 0; + st->vtbl = &incore_vtbl; + + return st->u.incore.buf ? 0 : -1; +} diff --git a/streaming.h b/streaming.h new file mode 100644 index 000000000..589e857b8 --- /dev/null +++ b/streaming.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2011, Google Inc. + */ +#ifndef STREAMING_H +#define STREAMING_H 1 +#include "cache.h" + +/* opaque */ +struct git_istream; + +extern struct git_istream *open_istream(const unsigned char *, enum object_type *, unsigned long *, struct stream_filter *); +extern int close_istream(struct git_istream *); +extern ssize_t read_istream(struct git_istream *, char *, size_t); + +#endif /* STREAMING_H */ diff --git a/submodule.c b/submodule.c index b6dec70bd..1ba9646d3 100644 --- a/submodule.c +++ b/submodule.c @@ -388,6 +388,7 @@ void check_for_new_submodule_commits(unsigned char new_sha1[20]) while (parent) { struct diff_options diff_opts; diff_setup(&diff_opts); + DIFF_OPT_SET(&diff_opts, RECURSIVE); diff_opts.output_format |= DIFF_FORMAT_CALLBACK; diff_opts.format_callback = submodule_collect_changed_cb; if (diff_setup_done(&diff_opts) < 0) diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh index 143eb1f24..292753f77 100644 --- a/t/gitweb-lib.sh +++ b/t/gitweb-lib.sh @@ -107,4 +107,9 @@ perl -MEncode -e '$e="";decode_utf8($e, Encode::FB_CROAK)' >/dev/null 2>&1 || { test_done } +perl -MCGI -MCGI::Util -MCGI::Carp -e 0 >/dev/null 2>&1 || { + skip_all='skipping gitweb tests, CGI module unusable' + test_done +} + gitweb_init diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 9078b84ae..f19e6510d 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -66,31 +66,48 @@ test_expect_success expanded_in_repo ' echo "\$Id:NoSpaceAtEitherEnd\$" echo "\$Id: NoTerminatingSymbol" echo "\$Id: Foreign Commit With Spaces \$" - echo "\$Id: NoTerminatingSymbolAtEOF" - } > expanded-keywords && + } >expanded-keywords.0 && + + { + cat expanded-keywords.0 && + printf "\$Id: NoTerminatingSymbolAtEOF" + } >expanded-keywords && + cat expanded-keywords >expanded-keywords-crlf && + git add expanded-keywords expanded-keywords-crlf && + git commit -m "File with keywords expanded" && + id=$(git rev-parse --verify :expanded-keywords) && { echo "File with expanded keywords" - echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" - echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" - echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" - echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" - echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" - echo "\$Id: fd0478f5f1486f3d5177d4c3f6eb2765e8fc56b9 \$" + echo "\$Id: $id \$" + echo "\$Id: $id \$" + echo "\$Id: $id \$" + echo "\$Id: $id \$" + echo "\$Id: $id \$" + echo "\$Id: $id \$" echo "\$Id: NoTerminatingSymbol" echo "\$Id: Foreign Commit With Spaces \$" - echo "\$Id: NoTerminatingSymbolAtEOF" - } > expected-output && - - git add expanded-keywords && - git commit -m "File with keywords expanded" && + } >expected-output.0 && + { + cat expected-output.0 && + printf "\$Id: NoTerminatingSymbolAtEOF" + } >expected-output && + { + append_cr <expected-output.0 && + printf "\$Id: NoTerminatingSymbolAtEOF" + } >expected-output-crlf && + { + echo "expanded-keywords ident" + echo "expanded-keywords-crlf ident text eol=crlf" + } >>.gitattributes && - echo "expanded-keywords ident" >> .gitattributes && + rm -f expanded-keywords expanded-keywords-crlf && - rm -f expanded-keywords && git checkout -- expanded-keywords && - cat expanded-keywords && - cmp expanded-keywords expected-output + test_cmp expanded-keywords expected-output && + + git checkout -- expanded-keywords-crlf && + test_cmp expanded-keywords-crlf expected-output-crlf ' # The use of %f in a filter definition is expanded to the path to diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index ae266147b..007f39d5e 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -28,6 +28,7 @@ String options --st <st> get another string (pervert ordering) -o <str> get another string --default-string set string to default + --list <str> add str to list Magic arguments --quux means --quux @@ -337,4 +338,20 @@ test_expect_success 'negation of OPT_NONEG flags is not ambiguous' ' test_cmp expect output ' +cat >>expect <<'EOF' +list: foo +list: bar +list: baz +EOF +test_expect_success '--list keeps list of strings' ' + test-parse-options --list foo --list=bar --list=baz >output && + test_cmp expect output +' + +test_expect_success '--no-list resets list' ' + test-parse-options --list=other --list=irrelevant --list=options \ + --no-list --list=foo --list=bar --list=baz >output && + test_cmp expect output +' + test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index f6a44c9ee..865b8ed26 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -140,6 +140,22 @@ test_expect_success 'GIT_PREFIX for !alias' ' test_cmp expect actual ' +test_expect_success 'GIT_PREFIX for built-ins' ' + # Use GIT_EXTERNAL_DIFF to test that the "diff" built-in + # receives the GIT_PREFIX variable. + printf "dir/" >expect && + printf "#!/bin/sh\n" >diff && + printf "printf \"\$GIT_PREFIX\"" >>diff && + chmod +x diff && + ( + cd dir && + printf "change" >two && + env GIT_EXTERNAL_DIFF=./diff git diff >../actual + git checkout -- two + ) && + test_cmp expect actual +' + test_expect_success 'no file/rev ambiguity check inside .git' ' git commit -a -m 1 && ( diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3db56267e..3e140c18f 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -904,4 +904,22 @@ test_expect_success 'git -c works with aliases of builtins' ' test_cmp expect actual ' +test_expect_success 'git -c does not split values on equals' ' + echo "value with = in it" >expect && + git -c core.foo="value with = in it" config core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'git -c dies on bogus config' ' + test_must_fail git -c core.bare=foo rev-parse +' + +test_expect_success 'git -c complains about empty key' ' + test_must_fail git -c "=foo" rev-parse +' + +test_expect_success 'git -c complains about empty key and value' ' + test_must_fail git -c "" rev-parse +' + test_done diff --git a/t/t1412-reflog-loop.sh b/t/t1412-reflog-loop.sh index 7f519e5eb..647d88850 100755 --- a/t/t1412-reflog-loop.sh +++ b/t/t1412-reflog-loop.sh @@ -21,10 +21,10 @@ test_expect_success 'setup reflog with alternating commits' ' test_expect_success 'reflog shows all entries' ' cat >expect <<-\EOF - topic@{0} two: updating HEAD - topic@{1} one: updating HEAD - topic@{2} two: updating HEAD - topic@{3} one: updating HEAD + topic@{0} reset: moving to two + topic@{1} reset: moving to one + topic@{2} reset: moving to two + topic@{3} reset: moving to one topic@{4} branch: Created from HEAD EOF git log -g --format="%gd %gs" topic >actual && diff --git a/t/t2018-checkout-branch.sh b/t/t2018-checkout-branch.sh index fa6901638..a42e03967 100755 --- a/t/t2018-checkout-branch.sh +++ b/t/t2018-checkout-branch.sh @@ -169,4 +169,15 @@ test_expect_success 'checkout -f -B to an existing branch with mergeable changes test_must_fail test_dirty_mergeable ' +test_expect_success 'checkout -b <describe>' ' + git tag -f -m "First commit" initial initial && + git checkout -f change1 && + name=$(git describe) && + git checkout -b $name && + git diff --exit-code change1 && + echo "refs/heads/$name" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh new file mode 100755 index 000000000..09dcf043f --- /dev/null +++ b/t/t3103-ls-tree-misc.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +test_description=' +Miscellaneous tests for git ls-tree. + + 1. git ls-tree fails in presence of tree damage. + +' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir a && + touch a/one && + git add a/one && + git commit -m test +' + +test_expect_success 'ls-tree fails with non-zero exit code on broken tree' ' + rm -f .git/objects/5f/cffbd6e4c5c5b8d81f5e9314b20e338e3ffff5 && + test_must_fail git ls-tree -r HEAD +' + +test_done diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh index 2ea3be654..1aa366a41 100755 --- a/t/t3307-notes-man.sh +++ b/t/t3307-notes-man.sh @@ -26,13 +26,13 @@ test_expect_success 'example 1: notes to add an Acked-by line' ' ' test_expect_success 'example 2: binary notes' ' - cp "$TEST_DIRECTORY"/test4012.png . && + cp "$TEST_DIRECTORY"/test-binary-1.png . && git checkout B && - blob=$(git hash-object -w test4012.png) && + blob=$(git hash-object -w test-binary-1.png) && git notes --ref=logo add -C "$blob" && git notes --ref=logo copy B C && git notes --ref=logo show C >actual && - test_cmp test4012.png actual + test_cmp test-binary-1.png actual ' test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 47c8371c7..8538813d1 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -295,7 +295,7 @@ test_expect_success 'preserve merges with -p' ' ' test_expect_success 'edit ancestor with -p' ' - FAKE_LINES="1 edit 2 3 4" git rebase -i -p HEAD~3 && + FAKE_LINES="1 2 edit 3 4" git rebase -i -p HEAD~3 && echo 2 > unrelated-file && test_tick && git commit -m L2-modified --amend unrelated-file && diff --git a/t/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index 08201e233..6de4e2263 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -37,7 +37,15 @@ export GIT_AUTHOR_EMAIL # \ # B2 <-- origin/topic # -# In all cases, 'topic' is rebased onto 'origin/topic'. +# Clone 4 (merge using second parent as base): +# +# A1--A2--B3 <-- origin/master +# \ +# B1--A3--M <-- topic +# \ / +# \--A4 <-- topic2 +# \ +# B2 <-- origin/topic test_expect_success 'setup for merge-preserving rebase' \ 'echo First > A && @@ -57,6 +65,13 @@ test_expect_success 'setup for merge-preserving rebase' \ git merge origin/master ) && + git clone ./. clone4 && + ( + cd clone4 && + git checkout -b topic origin/topic && + git merge origin/master + ) && + echo Fifth > B && git add B && git commit -m "Add different B" && @@ -123,4 +138,15 @@ test_expect_success 'rebase -p preserves no-ff merges' ' ) ' +test_expect_success 'rebase -p works when base inside second parent' ' + ( + cd clone4 && + git fetch && + git rebase -p HEAD^2 && + test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && + test 1 = $(git rev-list --all --pretty=oneline | grep "Modify B" | wc -l) && + test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote-tracking branch " | wc -l) + ) +' + test_done diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh index 14a23cd87..ace8e54e9 100755 --- a/t/t3411-rebase-preserve-around-merges.sh +++ b/t/t3411-rebase-preserve-around-merges.sh @@ -37,7 +37,7 @@ test_expect_success 'setup' ' # -- C1 -- # test_expect_success 'squash F1 into D1' ' - FAKE_LINES="1 squash 3 2" git rebase -i -p B1 && + FAKE_LINES="1 squash 4 2 3" git rebase -i -p B1 && test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" && test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" && git tag E2 diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh new file mode 100755 index 000000000..4f2eedfd4 --- /dev/null +++ b/t/t3905-stash-include-untracked.sh @@ -0,0 +1,155 @@ +#!/bin/sh +# +# Copyright (c) 2011 David Caldwell +# + +test_description='Test git stash --include-untracked' + +. ./test-lib.sh + +test_expect_success 'stash save --include-untracked some dirty working directory' ' + echo 1 > file && + git add file && + test_tick && + git commit -m initial && + echo 2 > file && + git add file && + echo 3 > file && + test_tick && + echo 1 > file2 && + git stash --include-untracked && + git diff-files --quiet && + git diff-index --cached --quiet HEAD +' + +cat > expect <<EOF +?? expect +?? output +EOF + +test_expect_success 'stash save --include-untracked cleaned the untracked files' ' + git status --porcelain > output + test_cmp output expect +' + +cat > expect.diff <<EOF +diff --git a/file2 b/file2 +new file mode 100644 +index 0000000..d00491f +--- /dev/null ++++ b/file2 +@@ -0,0 +1 @@ ++1 +EOF +cat > expect.lstree <<EOF +file2 +EOF + +test_expect_success 'stash save --include-untracked stashed the untracked files' ' + test "!" -f file2 && + git diff HEAD..stash^3 -- file2 > output && + test_cmp output expect.diff && + git ls-tree --name-only stash^3: > output && + test_cmp output expect.lstree +' +test_expect_success 'stash save --patch --include-untracked fails' ' + test_must_fail git stash --patch --include-untracked +' + +test_expect_success 'stash save --patch --all fails' ' + test_must_fail git stash --patch --all +' + +git clean --force --quiet + +cat > expect <<EOF + M file +?? expect +?? file2 +?? output +EOF + +test_expect_success 'stash pop after save --include-untracked leaves files untracked again' ' + git stash pop && + git status --porcelain > output + test_cmp output expect +' + +git clean --force --quiet + +test_expect_success 'stash save -u dirty index' ' + echo 4 > file3 && + git add file3 && + test_tick && + git stash -u +' + +cat > expect <<EOF +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..b8626c4 +--- /dev/null ++++ b/file3 +@@ -0,0 +1 @@ ++4 +EOF + +test_expect_success 'stash save --include-untracked dirty index got stashed' ' + git stash pop --index && + git diff --cached > output && + test_cmp output expect +' + +git reset > /dev/null + +test_expect_success 'stash save --include-untracked -q is quiet' ' + echo 1 > file5 && + git stash save --include-untracked --quiet > output.out 2>&1 && + test ! -s output.out +' + +test_expect_success 'stash save --include-untracked removed files' ' + rm -f file && + git stash save --include-untracked && + echo 1 > expect && + test_cmp file expect +' + +rm -f expect + +test_expect_success 'stash save --include-untracked removed files got stashed' ' + git stash pop && + test ! -f file +' + +cat > .gitignore <<EOF +.gitignore +ignored +EOF + +test_expect_success 'stash save --include-untracked respects .gitignore' ' + echo ignored > ignored && + git stash -u && + test -s ignored && + test -s .gitignore +' + +test_expect_success 'stash save -u can stash with only untracked files different' ' + echo 4 > file4 && + git stash -u + test "!" -f file4 +' + +test_expect_success 'stash save --all does not respect .gitignore' ' + git stash -a && + test "!" -f ignored && + test "!" -f .gitignore +' + +test_expect_success 'stash save --all is stash poppable' ' + git stash pop && + test -s ignored && + test -s .gitignore +' + +test_done diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index 05ec06283..2d9f9a0cf 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -12,7 +12,7 @@ test_expect_success 'prepare repository' \ 'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d && git update-index --add a b c d && echo git >a && - cat "$TEST_DIRECTORY"/test4012.png >b && + cat "$TEST_DIRECTORY"/test-binary-1.png >b && echo git >c && cat b b >d' diff --git a/t/t4048-diff-combined-binary.sh b/t/t4048-diff-combined-binary.sh new file mode 100755 index 000000000..87a894950 --- /dev/null +++ b/t/t4048-diff-combined-binary.sh @@ -0,0 +1,212 @@ +#!/bin/sh + +test_description='combined and merge diff handle binary files and textconv' +. ./test-lib.sh + +test_expect_success 'setup binary merge conflict' ' + echo oneQ1 | q_to_nul >binary && + git add binary && + git commit -m one && + echo twoQ2 | q_to_nul >binary && + git commit -a -m two && + git checkout -b branch-binary HEAD^ && + echo threeQ3 | q_to_nul >binary && + git commit -a -m three && + test_must_fail git merge master && + echo resolvedQhooray | q_to_nul >binary && + git commit -a -m resolved +' + +cat >expect <<'EOF' +resolved + +diff --git a/binary b/binary +index 7ea6ded..9563691 100644 +Binary files a/binary and b/binary differ +resolved + +diff --git a/binary b/binary +index 6197570..9563691 100644 +Binary files a/binary and b/binary differ +EOF +test_expect_success 'diff -m indicates binary-ness' ' + git show --format=%s -m >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +resolved + +diff --combined binary +index 7ea6ded,6197570..9563691 +Binary files differ +EOF +test_expect_success 'diff -c indicates binary-ness' ' + git show --format=%s -c >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +resolved + +diff --cc binary +index 7ea6ded,6197570..9563691 +Binary files differ +EOF +test_expect_success 'diff --cc indicates binary-ness' ' + git show --format=%s --cc >actual && + test_cmp expect actual +' + +test_expect_success 'setup non-binary with binary attribute' ' + git checkout master && + test_commit one text && + test_commit two text && + git checkout -b branch-text HEAD^ && + test_commit three text && + test_must_fail git merge master && + test_commit resolved text && + echo text -diff >.gitattributes +' + +cat >expect <<'EOF' +resolved + +diff --git a/text b/text +index 2bdf67a..2ab19ae 100644 +Binary files a/text and b/text differ +resolved + +diff --git a/text b/text +index f719efd..2ab19ae 100644 +Binary files a/text and b/text differ +EOF +test_expect_success 'diff -m respects binary attribute' ' + git show --format=%s -m >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +resolved + +diff --combined text +index 2bdf67a,f719efd..2ab19ae +Binary files differ +EOF +test_expect_success 'diff -c respects binary attribute' ' + git show --format=%s -c >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +resolved + +diff --cc text +index 2bdf67a,f719efd..2ab19ae +Binary files differ +EOF +test_expect_success 'diff --cc respects binary attribute' ' + git show --format=%s --cc >actual && + test_cmp expect actual +' + +test_expect_success 'setup textconv attribute' ' + echo "text diff=upcase" >.gitattributes && + git config diff.upcase.textconv "tr a-z A-Z <" +' + +cat >expect <<'EOF' +resolved + +diff --git a/text b/text +index 2bdf67a..2ab19ae 100644 +--- a/text ++++ b/text +@@ -1 +1 @@ +-THREE ++RESOLVED +resolved + +diff --git a/text b/text +index f719efd..2ab19ae 100644 +--- a/text ++++ b/text +@@ -1 +1 @@ +-TWO ++RESOLVED +EOF +test_expect_success 'diff -m respects textconv attribute' ' + git show --format=%s -m >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +resolved + +diff --combined text +index 2bdf67a,f719efd..2ab19ae +--- a/text ++++ b/text +@@@ -1,1 -1,1 +1,1 @@@ +- THREE + -TWO +++RESOLVED +EOF +test_expect_success 'diff -c respects textconv attribute' ' + git show --format=%s -c >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +resolved + +diff --cc text +index 2bdf67a,f719efd..2ab19ae +--- a/text ++++ b/text +@@@ -1,1 -1,1 +1,1 @@@ +- THREE + -TWO +++RESOLVED +EOF +test_expect_success 'diff --cc respects textconv attribute' ' + git show --format=%s --cc >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +diff --combined text +index 2bdf67a,f719efd..2ab19ae +--- a/text ++++ b/text +@@@ -1,1 -1,1 +1,1 @@@ +- three + -two +++resolved +EOF +test_expect_success 'diff-tree plumbing does not respect textconv' ' + git diff-tree HEAD -c -p >full && + tail -n +2 full >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +diff --cc text +index 2bdf67a,f719efd..0000000 +--- a/text ++++ b/text +@@@ -1,1 -1,1 +1,5 @@@ +++<<<<<<< HEAD + +THREE +++======= ++ TWO +++>>>>>>> MASTER +EOF +test_expect_success 'diff --cc respects textconv on worktree file' ' + git reset --hard HEAD^ && + test_must_fail git merge master && + git diff >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4049-diff-stat-count.sh b/t/t4049-diff-stat-count.sh new file mode 100755 index 000000000..641e70d14 --- /dev/null +++ b/t/t4049-diff-stat-count.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Copyright (c) 2011, Google Inc. + +test_description='diff --stat-count' +. ./test-lib.sh + +test_expect_success setup ' + >a && + >b && + >c && + >d && + git add a b c d && + chmod +x c d && + echo a >a && + echo b >b && + cat >expect <<-\EOF + a | 1 + + b | 1 + + 2 files changed, 2 insertions(+), 0 deletions(-) + EOF + git diff --stat --stat-count=2 >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 850fc96d1..151404e09 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -465,7 +465,7 @@ test_expect_success 'am newline in subject' ' test_tick && sed -e "s/second/second \\\n foo/" patch1 >patchnl && git am <patchnl >output.out 2>&1 && - grep "^Applying: second \\\n foo$" output.out + test_i18ngrep "^Applying: second \\\n foo$" output.out ' test_expect_success 'am -q is quiet' ' diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index c95c4ccc3..1176bcccf 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -45,8 +45,9 @@ do test_expect_success "am$with3 --skip continue after failed am$with3" ' test_must_fail git am$with3 --skip >output && - test "$(grep "^Applying" output)" = "Applying: 6" && - test_cmp file-2-expect file-2 && + test_i18ngrep "^Applying" output >output.applying && + test_i18ngrep "^Applying: 6$" output.applying && + test_i18ncmp file-2-expect file-2 && test ! -f .git/MERGE_RR ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index cff1b3e05..d9068981f 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -26,6 +26,8 @@ commit id embedding: . ./test-lib.sh UNZIP=${UNZIP:-unzip} +GZIP=${GZIP:-gzip} +GUNZIP=${GUNZIP:-gzip -d} SUBSTFORMAT=%H%n @@ -252,4 +254,102 @@ test_expect_success 'git-archive --prefix=olde-' ' test -f h/olde-a/bin/sh ' +test_expect_success 'setup tar filters' ' + git config tar.tar.foo.command "tr ab ba" && + git config tar.bar.command "tr ab ba" && + git config tar.bar.remote true +' + +test_expect_success 'archive --list mentions user filter' ' + git archive --list >output && + grep "^tar\.foo\$" output && + grep "^bar\$" output +' + +test_expect_success NOT_MINGW 'archive --list shows only enabled remote filters' ' + git archive --list --remote=. >output && + ! grep "^tar\.foo\$" output && + grep "^bar\$" output +' + +test_expect_success 'invoke tar filter by format' ' + git archive --format=tar.foo HEAD >config.tar.foo && + tr ab ba <config.tar.foo >config.tar && + test_cmp b.tar config.tar && + git archive --format=bar HEAD >config.bar && + tr ab ba <config.bar >config.tar && + test_cmp b.tar config.tar +' + +test_expect_success 'invoke tar filter by extension' ' + git archive -o config-implicit.tar.foo HEAD && + test_cmp config.tar.foo config-implicit.tar.foo && + git archive -o config-implicit.bar HEAD && + test_cmp config.tar.foo config-implicit.bar +' + +test_expect_success 'default output format remains tar' ' + git archive -o config-implicit.baz HEAD && + test_cmp b.tar config-implicit.baz +' + +test_expect_success 'extension matching requires dot' ' + git archive -o config-implicittar.foo HEAD && + test_cmp b.tar config-implicittar.foo +' + +test_expect_success NOT_MINGW 'only enabled filters are available remotely' ' + test_must_fail git archive --remote=. --format=tar.foo HEAD \ + >remote.tar.foo && + git archive --remote=. --format=bar >remote.bar HEAD && + test_cmp remote.bar config.bar +' + +if $GZIP --version >/dev/null 2>&1; then + test_set_prereq GZIP +else + say "Skipping some tar.gz tests because gzip not found" +fi + +test_expect_success GZIP 'git archive --format=tgz' ' + git archive --format=tgz HEAD >j.tgz +' + +test_expect_success GZIP 'git archive --format=tar.gz' ' + git archive --format=tar.gz HEAD >j1.tar.gz && + test_cmp j.tgz j1.tar.gz +' + +test_expect_success GZIP 'infer tgz from .tgz filename' ' + git archive --output=j2.tgz HEAD && + test_cmp j.tgz j2.tgz +' + +test_expect_success GZIP 'infer tgz from .tar.gz filename' ' + git archive --output=j3.tar.gz HEAD && + test_cmp j.tgz j3.tar.gz +' + +if $GUNZIP --version >/dev/null 2>&1; then + test_set_prereq GUNZIP +else + say "Skipping some tar.gz tests because gunzip was not found" +fi + +test_expect_success GZIP,GUNZIP 'extract tgz file' ' + $GUNZIP -c <j.tgz >j.tar && + test_cmp b.tar j.tar +' + +test_expect_success GZIP,NOT_MINGW 'remote tar.gz is allowed by default' ' + git archive --remote=. --format=tar.gz HEAD >remote.tar.gz && + test_cmp j.tgz remote.tar.gz +' + +test_expect_success GZIP,NOT_MINGW 'remote tar.gz can be disabled' ' + git config tar.tar.gz.remote false && + test_must_fail git archive --remote=. --format=tar.gz HEAD \ + >remote.tar.gz +' + test_done diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index b34ea93a8..f8fa92446 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -65,6 +65,14 @@ test_expect_success \ 'cmp "test-1-${pack1}.idx" "1.idx" && cmp "test-2-${pack2}.idx" "2.idx"' +test_expect_success 'index-pack --verify on index version 1' ' + git index-pack --verify "test-1-${pack1}.pack" +' + +test_expect_success 'index-pack --verify on index version 2' ' + git index-pack --verify "test-2-${pack2}.pack" +' + test_expect_success \ 'index v2: force some 64-bit offsets with pack-objects' \ 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)' @@ -93,6 +101,16 @@ test_expect_success OFF64_T \ '64-bit offsets: index-pack result should match pack-objects one' \ 'cmp "test-3-${pack3}.idx" "3.idx"' +test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2 (cheat)' ' + # This cheats by knowing which lower offset should still be encoded + # in 64-bit representation. + git index-pack --verify --index-version=2,0x40000 "test-3-${pack3}.pack" +' + +test_expect_success OFF64_T 'index-pack --verify on 64-bit offset v2' ' + git index-pack --verify "test-3-${pack3}.pack" +' + # returns the object number for given object in given pack index index_obj_nr() { @@ -208,9 +226,8 @@ test_expect_success \ ( while read obj do git cat-file -p $obj >/dev/null || exit 1 done <obj-list ) && - err=$(test_must_fail git verify-pack \ - ".git/objects/pack/pack-${pack1}.pack" 2>&1) && - echo "$err" | grep "CRC mismatch"' + test_must_fail git verify-pack ".git/objects/pack/pack-${pack1}.pack" +' test_expect_success 'running index-pack in the object store' ' rm -f .git/objects/pack/* && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index a1fddd4d1..ca5b027c5 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -47,7 +47,7 @@ test_expect_success setup ' git init && echo subcontent > subfile && git add subfile && - git submodule add "$pwd/deepsubmodule" deepsubmodule && + git submodule add "$pwd/deepsubmodule" subdir/deepsubmodule && git commit -a -m new ) && git submodule add "$pwd/submodule" submodule && @@ -58,7 +58,7 @@ test_expect_success setup ' git submodule update --init --recursive ) && echo "Fetching submodule submodule" > expect.out && - echo "Fetching submodule submodule/deepsubmodule" >> expect.out + echo "Fetching submodule submodule/subdir/deepsubmodule" >> expect.out ' test_expect_success "fetch --recurse-submodules recurses into submodules" ' @@ -277,12 +277,12 @@ test_expect_success "Recursion picks up all submodules when necessary" ' ( cd submodule && ( - cd deepsubmodule && + cd subdir/deepsubmodule && git fetch && git checkout -q FETCH_HEAD ) && head1=$(git rev-parse --short HEAD^) && - git add deepsubmodule && + git add subdir/deepsubmodule && git commit -m "new deepsubmodule" head2=$(git rev-parse --short HEAD) && echo "From $pwd/submodule" > ../expect.err.sub && @@ -309,12 +309,12 @@ test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no ne ( cd submodule && ( - cd deepsubmodule && + cd subdir/deepsubmodule && git fetch && git checkout -q FETCH_HEAD ) && head1=$(git rev-parse --short HEAD^) && - git add deepsubmodule && + git add subdir/deepsubmodule && git commit -m "new deepsubmodule" head2=$(git rev-parse --short HEAD) && echo "From $pwd/submodule" > ../expect.err.sub && @@ -345,13 +345,13 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess git config fetch.recurseSubmodules false && ( cd submodule && - git config -f .gitmodules submodule.deepsubmodule.fetchRecursive false + git config -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive false ) && git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err && git config --unset fetch.recurseSubmodules ( cd submodule && - git config --unset -f .gitmodules submodule.deepsubmodule.fetchRecursive + git config --unset -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive ) ) && test_i18ncmp expect.out actual.out && diff --git a/t/t5707-clone-detached.sh b/t/t5707-clone-detached.sh new file mode 100755 index 000000000..8b0d607df --- /dev/null +++ b/t/t5707-clone-detached.sh @@ -0,0 +1,76 @@ +#!/bin/sh + +test_description='test cloning a repository with detached HEAD' +. ./test-lib.sh + +head_is_detached() { + git --git-dir=$1/.git rev-parse --verify HEAD && + test_must_fail git --git-dir=$1/.git symbolic-ref HEAD +} + +test_expect_success 'setup' ' + echo one >file && + git add file && + git commit -m one && + echo two >file && + git commit -a -m two && + git tag two && + echo three >file && + git commit -a -m three +' + +test_expect_success 'clone repo (detached HEAD points to branch)' ' + git checkout master^0 && + git clone "file://$PWD" detached-branch +' +test_expect_success 'cloned HEAD matches' ' + echo three >expect && + git --git-dir=detached-branch/.git log -1 --format=%s >actual && + test_cmp expect actual +' +test_expect_failure 'cloned HEAD is detached' ' + head_is_detached detached-branch +' + +test_expect_success 'clone repo (detached HEAD points to tag)' ' + git checkout two^0 && + git clone "file://$PWD" detached-tag +' +test_expect_success 'cloned HEAD matches' ' + echo two >expect && + git --git-dir=detached-tag/.git log -1 --format=%s >actual && + test_cmp expect actual +' +test_expect_success 'cloned HEAD is detached' ' + head_is_detached detached-tag +' + +test_expect_success 'clone repo (detached HEAD points to history)' ' + git checkout two^ && + git clone "file://$PWD" detached-history +' +test_expect_success 'cloned HEAD matches' ' + echo one >expect && + git --git-dir=detached-history/.git log -1 --format=%s >actual && + test_cmp expect actual +' +test_expect_success 'cloned HEAD is detached' ' + head_is_detached detached-history +' + +test_expect_success 'clone repo (orphan detached HEAD)' ' + git checkout master^0 && + echo four >file && + git commit -a -m four && + git clone "file://$PWD" detached-orphan +' +test_expect_success 'cloned HEAD matches' ' + echo four >expect && + git --git-dir=detached-orphan/.git log -1 --format=%s >actual && + test_cmp expect actual +' +test_expect_success 'cloned HEAD is detached' ' + head_is_detached detached-orphan +' + +test_done diff --git a/t/t5708-clone-config.sh b/t/t5708-clone-config.sh new file mode 100755 index 000000000..27d730c0a --- /dev/null +++ b/t/t5708-clone-config.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +test_description='tests for git clone -c key=value' +. ./test-lib.sh + +test_expect_success 'clone -c sets config in cloned repo' ' + rm -rf child && + git clone -c core.foo=bar . child && + echo bar >expect && + git --git-dir=child/.git config core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'clone -c can set multi-keys' ' + rm -rf child && + git clone -c core.foo=bar -c core.foo=baz . child && + { echo bar; echo baz; } >expect && + git --git-dir=child/.git config --get-all core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'clone -c without a value is boolean true' ' + rm -rf child && + git clone -c core.foo . child && + echo true >expect && + git --git-dir=child/.git config --bool core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'clone -c config is available during clone' ' + echo content >file && + git add file && + git commit -m one && + rm -rf child && + git clone -c core.autocrlf . child && + printf "content\\r\\n" >expect && + test_cmp expect child/file +' + +test_done diff --git a/t/t5800-remote-helpers.sh b/t/t5800-remote-helpers.sh index 1fb6380fc..1c62001fc 100755 --- a/t/t5800-remote-helpers.sh +++ b/t/t5800-remote-helpers.sh @@ -7,17 +7,27 @@ test_description='Test remote-helper import and export commands' . ./test-lib.sh -if test_have_prereq PYTHON && "$PYTHON_PATH" -c ' +if ! test_have_prereq PYTHON ; then + skip_all='skipping git-remote-hg tests, python not available' + test_done +fi + +"$PYTHON_PATH" -c ' import sys if sys.hexversion < 0x02040000: sys.exit(1) -' -then - # Requires Python 2.4 or newer - test_set_prereq PYTHON_24 -fi +' || { + skip_all='skipping git-remote-hg tests, python version < 2.4' + test_done +} + +compare_refs() { + git --git-dir="$1/.git" rev-parse --verify $2 >expect && + git --git-dir="$3/.git" rev-parse --verify $4 >actual && + test_cmp expect actual +} -test_expect_success PYTHON_24 'setup repository' ' +test_expect_success 'setup repository' ' git init --bare server/.git && git clone server public && (cd public && @@ -27,54 +37,99 @@ test_expect_success PYTHON_24 'setup repository' ' git push origin master) ' -test_expect_success PYTHON_24 'cloning from local repo' ' +test_expect_success 'cloning from local repo' ' git clone "testgit::${PWD}/server" localclone && test_cmp public/file localclone/file ' -test_expect_success PYTHON_24 'cloning from remote repo' ' +test_expect_success 'cloning from remote repo' ' git clone "testgit::file://${PWD}/server" clone && test_cmp public/file clone/file ' -test_expect_success PYTHON_24 'create new commit on remote' ' +test_expect_success 'create new commit on remote' ' (cd public && echo content >>file && git commit -a -m two && git push) ' -test_expect_success PYTHON_24 'pulling from local repo' ' +test_expect_success 'pulling from local repo' ' (cd localclone && git pull) && test_cmp public/file localclone/file ' -test_expect_success PYTHON_24 'pulling from remote remote' ' +test_expect_success 'pulling from remote remote' ' (cd clone && git pull) && test_cmp public/file clone/file ' -test_expect_success PYTHON_24 'pushing to local repo' ' +test_expect_success 'pushing to local repo' ' (cd localclone && echo content >>file && git commit -a -m three && git push) && - HEAD=$(git --git-dir=localclone/.git rev-parse --verify HEAD) && - test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD) + compare_refs localclone HEAD server HEAD ' -test_expect_success PYTHON_24 'synch with changes from localclone' ' +test_expect_success 'synch with changes from localclone' ' (cd clone && git pull) ' -test_expect_success PYTHON_24 'pushing remote local repo' ' +test_expect_success 'pushing remote local repo' ' (cd clone && echo content >>file && git commit -a -m four && git push) && - HEAD=$(git --git-dir=clone/.git rev-parse --verify HEAD) && - test $HEAD = $(git --git-dir=server/.git rev-parse --verify HEAD) + compare_refs clone HEAD server HEAD +' + +test_expect_success 'fetch new branch' ' + (cd public && + git checkout -b new && + echo content >>file && + git commit -a -m five && + git push origin new + ) && + (cd localclone && + git fetch origin new + ) && + compare_refs public HEAD localclone FETCH_HEAD +' + +test_expect_success 'fetch multiple branches' ' + (cd localclone && + git fetch + ) && + compare_refs server master localclone refs/remotes/origin/master && + compare_refs server new localclone refs/remotes/origin/new +' + +test_expect_success 'push when remote has extra refs' ' + (cd clone && + echo content >>file && + git commit -a -m six && + git push + ) && + compare_refs clone master server master +' + +test_expect_success 'push new branch by name' ' + (cd clone && + git checkout -b new-name && + echo content >>file && + git commit -a -m seven && + git push origin new-name + ) && + compare_refs clone HEAD server refs/heads/new-name +' + +test_expect_failure 'push new branch with old:new refspec' ' + (cd clone && + git push origin new-name:new-refspec + ) && + compare_refs clone HEAD server refs/heads/new-refspec ' test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index d9f343942..432f086c0 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -154,7 +154,7 @@ test_expect_success "expected conflict markers" "test_cmp expect out" test_expect_success 'binary files cannot be merged' ' test_must_fail git merge-file -p \ - orig.txt "$TEST_DIRECTORY"/test4012.png new1.txt 2> merge.err && + orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err && grep "Cannot merge binary files" merge.err ' diff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh index b519626ca..07735410b 100755 --- a/t/t6027-merge-binary.sh +++ b/t/t6027-merge-binary.sh @@ -6,7 +6,7 @@ test_description='ask merge-recursive to merge binary files' test_expect_success setup ' - cat "$TEST_DIRECTORY"/test4012.png >m && + cat "$TEST_DIRECTORY"/test-binary-1.png >m && git add m && git ls-files -s | sed -e "s/ 0 / 1 /" >E1 && test_tick && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 2ac1c6607..097ce2bc8 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -257,6 +257,11 @@ test_expect_success \ test_cmp expect actual ' +test_expect_success 'tag -l can accept multiple patterns' ' + git tag -l "v1*" "v0*" >actual && + test_cmp expect actual +' + # creating and verifying lightweight tags: test_expect_success \ diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index f1cfc9ac9..b096dc88c 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -429,6 +429,21 @@ test_expect_success '--mixed refreshes the index' ' test_i18ncmp expect output ' +test_expect_success 'resetting specific path that is unmerged' ' + git rm --cached file2 && + F1=$(git rev-parse HEAD:file1) && + F2=$(git rev-parse HEAD:file2) && + F3=$(git rev-parse HEAD:secondfile) && + { + echo "100644 $F1 1 file2" && + echo "100644 $F2 2 file2" && + echo "100644 $F3 3 file2" + } | git update-index --index-info && + git ls-files -u && + test_must_fail git reset HEAD file2 && + git diff-index --exit-code --cached HEAD +' + test_expect_success 'disambiguation (1)' ' git reset --hard && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 3f115d9ca..69115269c 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -77,7 +77,8 @@ test_expect_success 'submodule add' ' ( cd addtest && - git submodule add "$submodurl" submod && + git submodule add -q "$submodurl" submod >actual && + test ! -s actual && git submodule init ) && @@ -101,7 +102,7 @@ test_expect_success 'submodule add to .gitignored path fails' ' git add --force .gitignore && git commit -m"Ignore everything" && ! git submodule add "$submodurl" submod >actual 2>&1 && - test_cmp expect actual + test_i18ncmp expect actual ) ' @@ -275,7 +276,8 @@ test_expect_success 'update should work when path is an empty dir' ' echo "$rev1" >expect && mkdir init && - git submodule update && + git submodule update -q >update.out && + test ! -s update.out && inspect init && test_cmp expect head-sha1 @@ -359,7 +361,7 @@ test_expect_success 'update --init' ' git submodule update init > update.out && cat update.out && - grep "not initialized" update.out && + test_i18ngrep "not initialized" update.out && ! test -d init/.git && git submodule update --init init && @@ -448,6 +450,16 @@ test_expect_success 'add should fail when path is used by an existing directory' ) ' +test_expect_success 'use superproject as upstream when path is relative and no url is set there' ' + ( + cd addtest && + git submodule add ../repo relative && + test "$(git config -f .gitmodules submodule.relative.url)" = ../repo && + git submodule sync relative && + test "$(git config submodule.relative.url)" = "$submodurl/repo" + ) +' + test_expect_success 'set up for relative path tests' ' mkdir reltest && ( diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 7d7fde057..30b429e7d 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -128,7 +128,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' " < Add foo5 EOF - test_cmp actual expected + test_i18ncmp actual expected " test_expect_success 'typechanged submodule(submodule->blob), --files' " @@ -138,7 +138,7 @@ test_expect_success 'typechanged submodule(submodule->blob), --files' " > Add foo5 EOF - test_cmp actual expected + test_i18ncmp actual expected " rm -rf sm1 && @@ -149,7 +149,7 @@ test_expect_success 'typechanged submodule(submodule->blob)' " * sm1 $head4(submodule)->$head5(blob): EOF - test_cmp actual expected + test_i18ncmp actual expected " rm -f sm1 && @@ -162,7 +162,7 @@ test_expect_success 'nonexistent commit' " Warn: sm1 doesn't contain commit $head4_full EOF - test_cmp actual expected + test_i18ncmp actual expected " commit_file @@ -173,7 +173,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' " > Add foo7 EOF - test_cmp expected actual + test_i18ncmp expected actual " commit_file sm1 && @@ -228,7 +228,7 @@ EOF test_expect_success '--for-status' " git submodule summary --for-status HEAD^ >actual && - test_cmp actual - <<EOF + test_i18ncmp actual - <<EOF # Submodule changes to be committed: # # * sm1 $head6...0000000: diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index d600583ce..95ffe349a 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -25,7 +25,8 @@ test_expect_success setup ' git clone super super-clone && (cd super-clone && git submodule update --init) && git clone super empty-clone && - (cd empty-clone && git submodule init) + (cd empty-clone && git submodule init) && + git clone super top-only-clone ' test_expect_success 'change submodule' ' @@ -66,7 +67,7 @@ test_expect_success '"git submodule sync" should update submodule URLs' ' ) ' -test_expect_success '"git submodule sync" should update submodule URLs if not yet cloned' ' +test_expect_success '"git submodule sync" should update known submodule URLs' ' (cd empty-clone && git pull && git submodule sync && @@ -74,4 +75,14 @@ test_expect_success '"git submodule sync" should update submodule URLs if not ye ) ' +test_expect_success '"git submodule sync" should not vivify uninteresting submodule' ' + (cd top-only-clone && + git pull && + git submodule sync && + test -z "$(git config submodule.submodule.url)" && + git submodule sync submodule && + test -z "$(git config submodule.submodule.url)" + ) +' + test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 4f16fcce2..c679f36a4 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -90,7 +90,7 @@ test_expect_success 'submodule update does not fetch already present commits' ' (cd super && git submodule update > ../actual 2> ../actual.err ) && - test_cmp expected actual && + test_i18ncmp expected actual && ! test -s actual.err ' @@ -298,4 +298,148 @@ test_expect_success 'submodule update ignores update=rebase config for new submo ) ' +test_expect_success 'submodule update continues after checkout error' ' + (cd super && + git reset --hard HEAD && + git submodule add ../submodule submodule2 && + git submodule init && + git commit -am "new_submodule" && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../expect + ) && + (cd submodule && + test_commit "update_submodule" file + ) && + (cd submodule2 && + test_commit "update_submodule2" file + ) && + git add submodule && + git add submodule2 && + git commit -m "two_new_submodule_commits" && + (cd submodule && + echo "" > file + ) && + git checkout HEAD^ && + test_must_fail git submodule update && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../actual + ) && + test_cmp expect actual + ) +' +test_expect_success 'submodule update continues after recursive checkout error' ' + (cd super && + git reset --hard HEAD && + git checkout master && + git submodule update && + (cd submodule && + git submodule add ../submodule subsubmodule && + git submodule init && + git commit -m "new_subsubmodule" + ) && + git add submodule && + git commit -m "update_submodule" && + (cd submodule && + (cd subsubmodule && + test_commit "update_subsubmodule" file + ) && + git add subsubmodule && + test_commit "update_submodule_again" file && + (cd subsubmodule && + test_commit "update_subsubmodule_again" file + ) && + test_commit "update_submodule_again_again" file + ) && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../expect && + test_commit "update_submodule2_again" file + ) && + git add submodule && + git add submodule2 && + git commit -m "new_commits" && + git checkout HEAD^ && + (cd submodule && + git checkout HEAD^ && + (cd subsubmodule && + echo "" > file + ) + ) && + test_must_fail git submodule update --recursive && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../actual + ) && + test_cmp expect actual + ) +' + +test_expect_success 'submodule update exit immediately in case of merge conflict' ' + (cd super && + git checkout master && + git reset --hard HEAD && + (cd submodule && + (cd subsubmodule && + git reset --hard HEAD + ) + ) && + git submodule update --recursive && + (cd submodule && + test_commit "update_submodule_2" file + ) && + (cd submodule2 && + test_commit "update_submodule2_2" file + ) && + git add submodule && + git add submodule2 && + git commit -m "two_new_submodule_commits" && + (cd submodule && + git checkout master && + test_commit "conflict" file && + echo "conflict" > file + ) && + git checkout HEAD^ && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../expect + ) && + git config submodule.submodule.update merge && + test_must_fail git submodule update && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../actual + ) && + test_cmp expect actual + ) +' +test_expect_success 'submodule update exit immediately after recursive rebase error' ' + (cd super && + git checkout master && + git reset --hard HEAD && + (cd submodule && + git reset --hard HEAD && + git submodule update --recursive + ) && + (cd submodule && + test_commit "update_submodule_3" file + ) && + (cd submodule2 && + test_commit "update_submodule2_3" file + ) && + git add submodule && + git add submodule2 && + git commit -m "two_new_submodule_commits" && + (cd submodule && + git checkout master && + test_commit "conflict2" file && + echo "conflict" > file + ) && + git checkout HEAD^ && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../expect + ) && + git config submodule.submodule.update rebase && + test_must_fail git submodule update && + (cd submodule2 && + git rev-parse --max-count=1 HEAD > ../actual + ) && + test_cmp expect actual + ) +' test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index e5be13c27..be745fb23 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -77,7 +77,7 @@ test_expect_success 'test basic "submodule foreach" usage' ' git config foo.bar zar && git submodule foreach "git config --file \"\$toplevel/.git/config\" foo.bar" ) && - test_cmp expect actual + test_i18ncmp expect actual ' test_expect_success 'setup nested submodules' ' @@ -158,7 +158,7 @@ test_expect_success 'test messages from "foreach --recursive"' ' cd clone2 && git submodule foreach --recursive "true" > ../actual ) && - test_cmp expect actual + test_i18ncmp expect actual ' cat > expect <<EOF @@ -292,4 +292,22 @@ test_expect_success 'use "update --recursive nested1" to checkout all submodules ) ' +test_expect_success 'command passed to foreach retains notion of stdin' ' + ( + cd super && + git submodule foreach echo success >../expected && + yes | git submodule foreach "read y && test \"x\$y\" = xy && echo success" >../actual + ) && + test_cmp expected actual +' + +test_expect_success 'command passed to foreach --recursive retains notion of stdin' ' + ( + cd clone2 && + git submodule foreach --recursive echo success >../expected && + yes | git submodule foreach --recursive "read y && test \"x\$y\" = xy && echo success" >../actual + ) && + test_cmp expected actual +' + test_done diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index 8528f64c8..ee7f0cd45 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -84,5 +84,38 @@ test_expect_success POSIXPERM '--no-verify with non-executable hook' ' git commit --no-verify -m "more content" ' +chmod +x "$HOOK" + +# a hook that checks $GIT_PREFIX and succeeds inside the +# success/ subdirectory only +cat > "$HOOK" <<EOF +#!/bin/sh +test \$GIT_PREFIX = success/ +EOF + +test_expect_success 'with hook requiring GIT_PREFIX' ' + + echo "more content" >> file && + git add file && + mkdir success && + ( + cd success && + git commit -m "hook requires GIT_PREFIX = success/" + ) && + rmdir success +' + +test_expect_success 'with failing hook requiring GIT_PREFIX' ' + + echo "more content" >> file && + git add file && + mkdir fail && + ( + cd fail && + test_must_fail git commit -m "hook must fail" + ) && + rmdir fail && + git checkout -- file +' test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 1fdfbd386..905255adf 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -131,6 +131,127 @@ test_expect_success 'status -s' ' ' +test_expect_success 'status with gitignore' ' + { + echo ".gitignore" && + echo "expect" && + echo "output" && + echo "untracked" + } >.gitignore && + + cat >expect <<-\EOF && + M dir1/modified + A dir2/added + ?? dir2/modified + EOF + git status -s >output && + test_cmp expect output && + + cat >expect <<-\EOF && + M dir1/modified + A dir2/added + ?? dir2/modified + !! .gitignore + !! dir1/untracked + !! dir2/untracked + !! expect + !! output + !! untracked + EOF + git status -s --ignored >output && + test_cmp expect output && + + cat >expect <<-\EOF && + # On branch master + # Changes to be committed: + # (use "git reset HEAD <file>..." to unstage) + # + # new file: dir2/added + # + # Changes not staged for commit: + # (use "git add <file>..." to update what will be committed) + # (use "git checkout -- <file>..." to discard changes in working directory) + # + # modified: dir1/modified + # + # Untracked files: + # (use "git add <file>..." to include in what will be committed) + # + # dir2/modified + # Ignored files: + # (use "git add -f <file>..." to include in what will be committed) + # + # .gitignore + # dir1/untracked + # dir2/untracked + # expect + # output + # untracked + EOF + git status --ignored >output && + test_cmp expect output +' + +test_expect_success 'status with gitignore (nothing untracked)' ' + { + echo ".gitignore" && + echo "expect" && + echo "dir2/modified" && + echo "output" && + echo "untracked" + } >.gitignore && + + cat >expect <<-\EOF && + M dir1/modified + A dir2/added + EOF + git status -s >output && + test_cmp expect output && + + cat >expect <<-\EOF && + M dir1/modified + A dir2/added + !! .gitignore + !! dir1/untracked + !! dir2/modified + !! dir2/untracked + !! expect + !! output + !! untracked + EOF + git status -s --ignored >output && + test_cmp expect output && + + cat >expect <<-\EOF && + # On branch master + # Changes to be committed: + # (use "git reset HEAD <file>..." to unstage) + # + # new file: dir2/added + # + # Changes not staged for commit: + # (use "git add <file>..." to update what will be committed) + # (use "git checkout -- <file>..." to discard changes in working directory) + # + # modified: dir1/modified + # + # Ignored files: + # (use "git add -f <file>..." to include in what will be committed) + # + # .gitignore + # dir1/untracked + # dir2/modified + # dir2/untracked + # expect + # output + # untracked + EOF + git status --ignored >output && + test_cmp expect output +' + +rm -f .gitignore + cat >expect <<\EOF ## master M dir1/modified diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 6379ad60b..a29ae45b3 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -716,4 +716,99 @@ test_expect_success LIBPCRE 'grep -G -F -E -P pattern' ' test_cmp expected actual ' +test_config() { + git config "$1" "$2" && + test_when_finished "git config --unset $1" +} + +cat >expected <<EOF +hello.c<RED>:<RESET>int main(int argc, const char **argv) +hello.c<RED>-<RESET>{ +<RED>--<RESET> +hello.c<RED>:<RESET> /* char ?? */ +hello.c<RED>-<RESET>} +<RED>--<RESET> +hello_world<RED>:<RESET>Hello_world +hello_world<RED>-<RESET>HeLLo_world +EOF + +test_expect_success 'grep --color, separator' ' + test_config color.grep.context normal && + test_config color.grep.filename normal && + test_config color.grep.function normal && + test_config color.grep.linenumber normal && + test_config color.grep.match normal && + test_config color.grep.selected normal && + test_config color.grep.separator red && + + git grep --color=always -A1 -e char -e lo_w hello.c hello_world | + test_decode_color >actual && + test_cmp expected actual +' + +cat >expected <<EOF +hello.c:int main(int argc, const char **argv) +hello.c: /* char ?? */ + +hello_world:Hello_world +EOF + +test_expect_success 'grep --break' ' + git grep --break -e char -e lo_w hello.c hello_world >actual && + test_cmp expected actual +' + +cat >expected <<EOF +hello.c:int main(int argc, const char **argv) +hello.c-{ +-- +hello.c: /* char ?? */ +hello.c-} + +hello_world:Hello_world +hello_world-HeLLo_world +EOF + +test_expect_success 'grep --break with context' ' + git grep --break -A1 -e char -e lo_w hello.c hello_world >actual && + test_cmp expected actual +' + +cat >expected <<EOF +hello.c +int main(int argc, const char **argv) + /* char ?? */ +hello_world +Hello_world +EOF + +test_expect_success 'grep --heading' ' + git grep --heading -e char -e lo_w hello.c hello_world >actual && + test_cmp expected actual +' + +cat >expected <<EOF +<BOLD;GREEN>hello.c<RESET> +2:int main(int argc, const <BLACK;BYELLOW>char<RESET> **argv) +6: /* <BLACK;BYELLOW>char<RESET> ?? */ + +<BOLD;GREEN>hello_world<RESET> +3:Hel<BLACK;BYELLOW>lo_w<RESET>orld +EOF + +test_expect_success 'mimic ack-grep --group' ' + test_config color.grep.context normal && + test_config color.grep.filename "bold green" && + test_config color.grep.function normal && + test_config color.grep.linenumber normal && + test_config color.grep.match "black yellow" && + test_config color.grep.selected normal && + test_config color.grep.separator normal && + + git grep --break --heading -n --color \ + -e char -e lo_w hello.c hello_world | + test_decode_color >actual && + test_cmp expected actual +' + test_done diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index e5da65b99..41db05cb4 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -50,8 +50,8 @@ test_expect_success \ 'mkdir A B C D E F && echo hello1 >A/newfile1.txt && echo hello2 >B/newfile2.txt && - cp "$TEST_DIRECTORY"/test9200a.png C/newfile3.png && - cp "$TEST_DIRECTORY"/test9200a.png D/newfile4.png && + cp "$TEST_DIRECTORY"/test-binary-1.png C/newfile3.png && + cp "$TEST_DIRECTORY"/test-binary-1.png D/newfile4.png && git add A/newfile1.txt && git add B/newfile2.txt && git add C/newfile3.png && @@ -76,8 +76,8 @@ test_expect_success \ rm -f B/newfile2.txt && rm -f C/newfile3.png && echo Hello5 >E/newfile5.txt && - cp "$TEST_DIRECTORY"/test9200b.png D/newfile4.png && - cp "$TEST_DIRECTORY"/test9200a.png F/newfile6.png && + cp "$TEST_DIRECTORY"/test-binary-2.png D/newfile4.png && + cp "$TEST_DIRECTORY"/test-binary-1.png F/newfile6.png && git add E/newfile5.txt && git add F/newfile6.png && git commit -a -m "Test: Remove, add and update" && @@ -165,7 +165,7 @@ test_expect_success \ 'mkdir "G g" && echo ok then >"G g/with spaces.txt" && git add "G g/with spaces.txt" && \ - cp "$TEST_DIRECTORY"/test9200a.png "G g/with spaces.png" && \ + cp "$TEST_DIRECTORY"/test-binary-1.png "G g/with spaces.png" && \ git add "G g/with spaces.png" && git commit -a -m "With spaces" && id=$(git rev-list --max-count=1 HEAD) && @@ -177,7 +177,7 @@ test_expect_success \ test_expect_success \ 'Update file with spaces in file name' \ 'echo Ok then >>"G g/with spaces.txt" && - cat "$TEST_DIRECTORY"/test9200a.png >>"G g/with spaces.png" && \ + cat "$TEST_DIRECTORY"/test-binary-1.png >>"G g/with spaces.png" && \ git add "G g/with spaces.png" && git commit -a -m "Update with spaces" && id=$(git rev-list --max-count=1 HEAD) && @@ -202,7 +202,7 @@ test_expect_success \ 'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö && echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt && - cp "$TEST_DIRECTORY"/test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && + cp "$TEST_DIRECTORY"/test-binary-1.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png && git commit -a -m "Går det så går det" && \ id=$(git rev-list --max-count=1 HEAD) && diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 2a53640c5..f25647502 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -2197,6 +2197,48 @@ test_expect_success 'R: quiet option results in no stats being output' ' test_cmp empty output ' +test_expect_success 'R: feature done means terminating "done" is mandatory' ' + echo feature done | test_must_fail git fast-import && + test_must_fail git fast-import --done </dev/null +' + +test_expect_success 'R: terminating "done" with trailing gibberish is ok' ' + git fast-import <<-\EOF && + feature done + done + trailing gibberish + EOF + git fast-import <<-\EOF + done + more trailing gibberish + EOF +' + +test_expect_success 'R: terminating "done" within commit' ' + cat >expect <<-\EOF && + OBJID + :000000 100644 OBJID OBJID A hello.c + :000000 100644 OBJID OBJID A hello2.c + EOF + git fast-import <<-EOF && + commit refs/heads/done-ends + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<EOT + Commit terminated by "done" command + EOT + M 100644 inline hello.c + data <<EOT + Hello, world. + EOT + C hello.c hello2.c + done + EOF + git rev-list done-ends | + git diff-tree -r --stdin --root --always | + sed -e "s/$_x40/OBJID/g" >actual && + test_cmp expect actual +' + cat >input <<EOF option git non-existing-option EOF diff --git a/t/t9800-git-p4.sh b/t/t9800-git-p4.sh index 33b012765..97ec9753b 100755 --- a/t/t9800-git-p4.sh +++ b/t/t9800-git-p4.sh @@ -45,29 +45,33 @@ test_expect_success 'add p4 files' ' cd "$TRASH_DIRECTORY" ' +cleanup_git() { + cd "$TRASH_DIRECTORY" && + rm -rf "$git" && + mkdir "$git" +} + test_expect_success 'basic git-p4 clone' ' "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && cd "$git" && git log --oneline >lines && - test_line_count = 1 lines && - cd .. && - rm -rf "$git" && mkdir "$git" + test_line_count = 1 lines ' test_expect_success 'git-p4 clone @all' ' "$GITP4" clone --dest="$git" //depot@all && + test_when_finished cleanup_git && cd "$git" && git log --oneline >lines && - test_line_count = 2 lines && - cd .. && - rm -rf "$git" && mkdir "$git" + test_line_count = 2 lines ' test_expect_success 'git-p4 sync uninitialized repo' ' test_create_repo "$git" && + test_when_finished cleanup_git && cd "$git" && - test_must_fail "$GITP4" sync && - rm -rf "$git" && mkdir "$git" + test_must_fail "$GITP4" sync ' # @@ -76,19 +80,18 @@ test_expect_success 'git-p4 sync uninitialized repo' ' # test_expect_success 'git-p4 sync new branch' ' test_create_repo "$git" && + test_when_finished cleanup_git && cd "$git" && test_commit head && "$GITP4" sync --branch=refs/remotes/p4/depot //depot@all && git log --oneline p4/depot >lines && - cat lines && - test_line_count = 2 lines && - cd .. && - rm -rf "$git" && mkdir "$git" + test_line_count = 2 lines ' test_expect_success 'exit when p4 fails to produce marshaled output' ' badp4dir="$TRASH_DIRECTORY/badp4dir" && mkdir -p "$badp4dir" && + test_when_finished "rm -rf $badp4dir" && cat >"$badp4dir"/p4 <<-EOF && #!$SHELL_PATH exit 1 @@ -106,29 +109,26 @@ test_expect_success 'add p4 files with wildcards in the names' ' echo file-wild-at >file-wild@at && echo file-wild-percent >file-wild%percent && p4 add -f file-wild* && - p4 submit -d "file wildcards" && - cd "$TRASH_DIRECTORY" + p4 submit -d "file wildcards" ' test_expect_success 'wildcard files git-p4 clone' ' "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && cd "$git" && test -f file-wild#hash && test -f file-wild\*star && test -f file-wild@at && - test -f file-wild%percent && - cd "$TRASH_DIRECTORY" && - rm -rf "$git" && mkdir "$git" + test -f file-wild%percent ' test_expect_success 'clone bare' ' "$GITP4" clone --dest="$git" --bare //depot && + test_when_finished cleanup_git && cd "$git" && test ! -d .git && bare=`git config --get core.bare` && - test "$bare" = true && - cd "$TRASH_DIRECTORY" && - rm -rf "$git" && mkdir "$git" + test "$bare" = true ' p4_add_user() { @@ -173,6 +173,7 @@ test_expect_success 'preserve users' ' p4_add_user bob Bob && p4_grant_admin alice && "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && cd "$git" && echo "username: a change by alice" >> file1 && echo "username: a change by bob" >> file2 && @@ -181,29 +182,28 @@ test_expect_success 'preserve users' ' git config git-p4.skipSubmitEditCheck true && P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit --preserve-user && p4_check_commit_author file1 alice && - p4_check_commit_author file2 bob && - cd "$TRASH_DIRECTORY" && - rm -rf "$git" && mkdir "$git" + p4_check_commit_author file2 bob ' # Test username support, submitting as bob, who lacks admin rights. Should # not submit change to p4 (git diff should show deltas). test_expect_success 'refuse to preserve users without perms' ' "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && cd "$git" && + git config git-p4.skipSubmitEditCheck true && echo "username-noperms: a change by alice" >> file1 && git commit --author "Alice <alice@localhost>" -m "perms: a change by alice" file1 && ! P4EDITOR=touch P4USER=bob P4PASSWD=secret "$GITP4" commit --preserve-user && - ! git diff --exit-code HEAD..p4/master > /dev/null && - cd "$TRASH_DIRECTORY" && - rm -rf "$git" && mkdir "$git" + ! git diff --exit-code HEAD..p4/master > /dev/null ' # What happens with unknown author? Without allowMissingP4Users it should fail. test_expect_success 'preserve user where author is unknown to p4' ' "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && cd "$git" && - git config git-p4.skipSubmitEditCheck true + git config git-p4.skipSubmitEditCheck true && echo "username-bob: a change by bob" >> file1 && git commit --author "Bob <bob@localhost>" -m "preserve: a change by bob" file1 && echo "username-unknown: a change by charlie" >> file1 && @@ -215,9 +215,7 @@ test_expect_success 'preserve user where author is unknown to p4' ' git config git-p4.preserveUser true && P4EDITOR=touch P4USER=alice P4PASSWD=secret "$GITP4" commit && git diff --exit-code HEAD..p4/master > /dev/null && - p4_check_commit_author file1 alice && - cd "$TRASH_DIRECTORY" && - rm -rf "$git" && mkdir "$git" + p4_check_commit_author file1 alice ' # If we're *not* using --preserve-user, git-p4 should warn if we're submitting @@ -226,33 +224,50 @@ test_expect_success 'preserve user where author is unknown to p4' ' # Test: warning disabled and user is the same. test_expect_success 'not preserving user with mixed authorship' ' "$GITP4" clone --dest="$git" //depot && - ( - cd "$git" && - git config git-p4.skipSubmitEditCheck true && - p4_add_user derek Derek && - - make_change_by_user usernamefile3 Derek derek@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - grep "git author derek@localhost does not match" actual && - - make_change_by_user usernamefile3 Charlie charlie@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - grep "git author charlie@localhost does not match" actual && - - make_change_by_user usernamefile3 alice alice@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - ! grep "git author.*does not match" actual && - - git config git-p4.skipUserNameCheck true && - make_change_by_user usernamefile3 Charlie charlie@localhost && - P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && - ! grep "git author.*does not match" actual && - - p4_check_commit_author usernamefile3 alice - ) && - rm -rf "$git" && mkdir "$git" + test_when_finished cleanup_git && + cd "$git" && + git config git-p4.skipSubmitEditCheck true && + p4_add_user derek Derek && + + make_change_by_user usernamefile3 Derek derek@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + grep "git author derek@localhost does not match" actual && + + make_change_by_user usernamefile3 Charlie charlie@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + grep "git author charlie@localhost does not match" actual && + + make_change_by_user usernamefile3 alice alice@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + ! grep "git author.*does not match" actual && + + git config git-p4.skipUserNameCheck true && + make_change_by_user usernamefile3 Charlie charlie@localhost && + P4EDITOR=cat P4USER=alice P4PASSWD=secret "$GITP4" commit >actual && + ! grep "git author.*does not match" actual && + + p4_check_commit_author usernamefile3 alice ' +marshal_dump() { + what=$1 + python -c 'import marshal, sys; d = marshal.load(sys.stdin); print d["'$what'"]' +} + +# Sleep a bit so that the top-most p4 change did not happen "now". Then +# import the repo and make sure that the initial import has the same time +# as the top-most change. +test_expect_success 'initial import time from top change time' ' + p4change=$(p4 -G changes -m 1 //depot/... | marshal_dump change) && + p4time=$(p4 -G changes -m 1 //depot/... | marshal_dump time) && + sleep 3 && + "$GITP4" clone --dest="$git" //depot && + test_when_finished cleanup_git && + cd "$git" && + gittime=$(git show -s --raw --pretty=format:%at HEAD) && + echo $p4time $gittime && + test $p4time = $gittime +' test_expect_success 'shutdown' ' pid=`pgrep -f p4d` && diff --git a/t/test4012.png b/t/test-binary-1.png Binary files differindex 7b181d15c..7b181d15c 100644 --- a/t/test4012.png +++ b/t/test-binary-1.png diff --git a/t/test9200b.png b/t/test-binary-2.png Binary files differindex ac22ccbd3..ac22ccbd3 100644 --- a/t/test9200b.png +++ b/t/test-binary-2.png diff --git a/t/test9200a.png b/t/test9200a.png Binary files differdeleted file mode 100644 index 7b181d15c..000000000 --- a/t/test9200a.png +++ /dev/null diff --git a/test-parse-options.c b/test-parse-options.c index 4e3710b9a..91a570165 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -1,5 +1,6 @@ #include "cache.h" #include "parse-options.h" +#include "string-list.h" static int boolean = 0; static int integer = 0; @@ -9,6 +10,7 @@ static int verbose = 0, dry_run = 0, quiet = 0; static char *string = NULL; static char *file = NULL; static int ambiguous; +static struct string_list list; static int length_callback(const struct option *opt, const char *arg, int unset) { @@ -54,6 +56,7 @@ int main(int argc, const char **argv) OPT_STRING('o', NULL, &string, "str", "get another string"), OPT_SET_PTR(0, "default-string", &string, "set string to default", (unsigned long)"default"), + OPT_STRING_LIST(0, "list", &list, "str", "add str to list"), OPT_GROUP("Magic arguments"), OPT_ARGUMENT("quux", "means --quux"), OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", @@ -85,6 +88,9 @@ int main(int argc, const char **argv) printf("dry run: %s\n", dry_run ? "yes" : "no"); printf("file: %s\n", file ? file : "(not set)"); + for (i = 0; i < list.nr; i++) + printf("list: %s\n", list.items[i].string); + for (i = 0; i < argc; i++) printf("arg %02d: %s\n", i, argv[i]); diff --git a/transport-helper.c b/transport-helper.c index 660147f9e..4eab844d4 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -23,6 +23,8 @@ struct helper_data { push : 1, connect : 1, no_disconnect_req : 1; + char *export_marks; + char *import_marks; /* These go from remote name (as in "list") to private name */ struct refspec *refspecs; int refspec_nr; @@ -105,6 +107,12 @@ static struct child_process *get_helper(struct transport *transport) int refspec_alloc = 0; int duped; int code; + char git_dir_buf[sizeof(GIT_DIR_ENVIRONMENT) + PATH_MAX + 1]; + const char *helper_env[] = { + git_dir_buf, + NULL + }; + if (data->helper) return data->helper; @@ -120,6 +128,10 @@ static struct child_process *get_helper(struct transport *transport) helper->argv[2] = remove_ext_force(transport->url); helper->git_cmd = 0; helper->silent_exec_failure = 1; + + snprintf(git_dir_buf, sizeof(git_dir_buf), "%s=%s", GIT_DIR_ENVIRONMENT, get_git_dir()); + helper->env = helper_env; + code = start_command(helper); if (code < 0 && errno == ENOENT) die("Unable to find remote helper for '%s'", data->name); @@ -171,14 +183,19 @@ static struct child_process *get_helper(struct transport *transport) ALLOC_GROW(refspecs, refspec_nr + 1, refspec_alloc); - refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); + refspecs[refspec_nr++] = strdup(capname + strlen("refspec ")); } else if (!strcmp(capname, "connect")) { data->connect = 1; - } else if (!strcmp(buf.buf, "gitdir")) { - struct strbuf gitdir = STRBUF_INIT; - strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir()); - sendline(data, &gitdir); - strbuf_release(&gitdir); + } else if (!prefixcmp(capname, "export-marks ")) { + struct strbuf arg = STRBUF_INIT; + strbuf_addstr(&arg, "--export-marks="); + strbuf_addstr(&arg, capname + strlen("export-marks ")); + data->export_marks = strbuf_detach(&arg, NULL); + } else if (!prefixcmp(capname, "import-marks")) { + struct strbuf arg = STRBUF_INIT; + strbuf_addstr(&arg, "--import-marks="); + strbuf_addstr(&arg, capname + strlen("import-marks ")); + data->import_marks = strbuf_detach(&arg, NULL); } else if (mandatory) { die("Unknown mandatory capability %s. This remote " "helper probably needs newer version of Git.\n", @@ -204,6 +221,7 @@ static int disconnect_helper(struct transport *transport) { struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; + int res = 0; if (data->helper) { if (debug) @@ -215,13 +233,13 @@ static int disconnect_helper(struct transport *transport) close(data->helper->in); close(data->helper->out); fclose(data->out); - finish_command(data->helper); + res = finish_command(data->helper); free((char *)data->helper->argv[0]); free(data->helper->argv); free(data->helper); data->helper = NULL; } - return 0; + return res; } static const char *unsupported_options[] = { @@ -299,12 +317,13 @@ static void standard_options(struct transport *t) static int release_helper(struct transport *transport) { + int res = 0; struct helper_data *data = transport->data; free_refspec(data->refspec_nr, data->refspecs); data->refspecs = NULL; - disconnect_helper(transport); + res = disconnect_helper(transport); free(transport->data); - return 0; + return res; } static int fetch_with_fetch(struct transport *transport, @@ -362,10 +381,9 @@ static int get_importer(struct transport *transport, struct child_process *fasti static int get_exporter(struct transport *transport, struct child_process *fastexport, - const char *export_marks, - const char *import_marks, struct string_list *revlist_args) { + struct helper_data *data = transport->data; struct child_process *helper = get_helper(transport); int argc = 0, i; memset(fastexport, 0, sizeof(*fastexport)); @@ -373,12 +391,13 @@ static int get_exporter(struct transport *transport, /* we need to duplicate helper->in because we want to use it after * fastexport is done with it. */ fastexport->out = dup(helper->in); - fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv)); + fastexport->argv = xcalloc(5 + revlist_args->nr, sizeof(*fastexport->argv)); fastexport->argv[argc++] = "fast-export"; - if (export_marks) - fastexport->argv[argc++] = export_marks; - if (import_marks) - fastexport->argv[argc++] = import_marks; + fastexport->argv[argc++] = "--use-done-feature"; + if (data->export_marks) + fastexport->argv[argc++] = data->export_marks; + if (data->import_marks) + fastexport->argv[argc++] = data->import_marks; for (i = 0; i < revlist_args->nr; i++) fastexport->argv[argc++] = revlist_args->items[i].string; @@ -410,8 +429,11 @@ static int fetch_with_import(struct transport *transport, sendline(data, &buf); strbuf_reset(&buf); } - disconnect_helper(transport); - finish_command(&fastimport); + + write_constant(data->helper->in, "\n"); + + if (finish_command(&fastimport)) + die("Error while running fast-import"); free(fastimport.argv); fastimport.argv = NULL; @@ -554,6 +576,88 @@ static int fetch(struct transport *transport, return -1; } +static void push_update_ref_status(struct strbuf *buf, + struct ref **ref, + struct ref *remote_refs) +{ + char *refname, *msg; + int status; + + if (!prefixcmp(buf->buf, "ok ")) { + status = REF_STATUS_OK; + refname = buf->buf + 3; + } else if (!prefixcmp(buf->buf, "error ")) { + status = REF_STATUS_REMOTE_REJECT; + refname = buf->buf + 6; + } else + die("expected ok/error, helper said '%s'\n", buf->buf); + + msg = strchr(refname, ' '); + if (msg) { + struct strbuf msg_buf = STRBUF_INIT; + const char *end; + + *msg++ = '\0'; + if (!unquote_c_style(&msg_buf, msg, &end)) + msg = strbuf_detach(&msg_buf, NULL); + else + msg = xstrdup(msg); + strbuf_release(&msg_buf); + + if (!strcmp(msg, "no match")) { + status = REF_STATUS_NONE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "up to date")) { + status = REF_STATUS_UPTODATE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "non-fast forward")) { + status = REF_STATUS_REJECT_NONFASTFORWARD; + free(msg); + msg = NULL; + } + } + + if (*ref) + *ref = find_ref_by_name(*ref, refname); + if (!*ref) + *ref = find_ref_by_name(remote_refs, refname); + if (!*ref) { + warning("helper reported unexpected status of %s", refname); + return; + } + + if ((*ref)->status != REF_STATUS_NONE) { + /* + * Earlier, the ref was marked not to be pushed, so ignore the ref + * status reported by the remote helper if the latter is 'no match'. + */ + if (status == REF_STATUS_NONE) + return; + } + + (*ref)->status = status; + (*ref)->remote_status = msg; +} + +static void push_update_refs_status(struct helper_data *data, + struct ref *remote_refs) +{ + struct strbuf buf = STRBUF_INIT; + struct ref *ref = remote_refs; + for (;;) { + recvline(data, &buf); + if (!buf.len) + break; + + push_update_ref_status(&buf, &ref, remote_refs); + } + strbuf_release(&buf); +} + static int push_refs_with_push(struct transport *transport, struct ref *remote_refs, int flags) { @@ -608,76 +712,9 @@ static int push_refs_with_push(struct transport *transport, strbuf_addch(&buf, '\n'); sendline(data, &buf); - - ref = remote_refs; - while (1) { - char *refname, *msg; - int status; - - recvline(data, &buf); - if (!buf.len) - break; - - if (!prefixcmp(buf.buf, "ok ")) { - status = REF_STATUS_OK; - refname = buf.buf + 3; - } else if (!prefixcmp(buf.buf, "error ")) { - status = REF_STATUS_REMOTE_REJECT; - refname = buf.buf + 6; - } else - die("expected ok/error, helper said '%s'\n", buf.buf); - - msg = strchr(refname, ' '); - if (msg) { - struct strbuf msg_buf = STRBUF_INIT; - const char *end; - - *msg++ = '\0'; - if (!unquote_c_style(&msg_buf, msg, &end)) - msg = strbuf_detach(&msg_buf, NULL); - else - msg = xstrdup(msg); - strbuf_release(&msg_buf); - - if (!strcmp(msg, "no match")) { - status = REF_STATUS_NONE; - free(msg); - msg = NULL; - } - else if (!strcmp(msg, "up to date")) { - status = REF_STATUS_UPTODATE; - free(msg); - msg = NULL; - } - else if (!strcmp(msg, "non-fast forward")) { - status = REF_STATUS_REJECT_NONFASTFORWARD; - free(msg); - msg = NULL; - } - } - - if (ref) - ref = find_ref_by_name(ref, refname); - if (!ref) - ref = find_ref_by_name(remote_refs, refname); - if (!ref) { - warning("helper reported unexpected status of %s", refname); - continue; - } - - if (ref->status != REF_STATUS_NONE) { - /* - * Earlier, the ref was marked not to be pushed, so ignore the ref - * status reported by the remote helper if the latter is 'no match'. - */ - if (status == REF_STATUS_NONE) - continue; - } - - ref->status = status; - ref->remote_status = msg; - } strbuf_release(&buf); + + push_update_refs_status(data, remote_refs); return 0; } @@ -687,7 +724,6 @@ static int push_refs_with_export(struct transport *transport, struct ref *ref; struct child_process *helper, exporter; struct helper_data *data = transport->data; - char *export_marks = NULL, *import_marks = NULL; struct string_list revlist_args = STRING_LIST_INIT_NODUP; struct strbuf buf = STRBUF_INIT; @@ -695,26 +731,6 @@ static int push_refs_with_export(struct transport *transport, write_constant(helper->in, "export\n"); - recvline(data, &buf); - if (debug) - fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf); - if (buf.len) { - struct strbuf arg = STRBUF_INIT; - strbuf_addstr(&arg, "--export-marks="); - strbuf_addbuf(&arg, &buf); - export_marks = strbuf_detach(&arg, NULL); - } - - recvline(data, &buf); - if (debug) - fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf); - if (buf.len) { - struct strbuf arg = STRBUF_INIT; - strbuf_addstr(&arg, "--import-marks="); - strbuf_addbuf(&arg, &buf); - import_marks = strbuf_detach(&arg, NULL); - } - strbuf_reset(&buf); for (ref = remote_refs; ref; ref = ref->next) { @@ -728,18 +744,23 @@ static int push_refs_with_export(struct transport *transport, strbuf_addf(&buf, "^%s", private); string_list_append(&revlist_args, strbuf_detach(&buf, NULL)); } + free(private); + + if (ref->deletion) { + die("remote-helpers do not support ref deletion"); + } - string_list_append(&revlist_args, ref->name); + if (ref->peer_ref) + string_list_append(&revlist_args, ref->peer_ref->name); } - if (get_exporter(transport, &exporter, - export_marks, import_marks, &revlist_args)) + if (get_exporter(transport, &exporter, &revlist_args)) die("Couldn't run fast-export"); - data->no_disconnect_req = 1; - finish_command(&exporter); - disconnect_helper(transport); + if (finish_command(&exporter)) + die("Error while running fast-export"); + push_update_refs_status(data, remote_refs); return 0; } diff --git a/unpack-trees.c b/unpack-trees.c index e22b9ec40..cc616c3f9 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -593,7 +593,7 @@ static int unpack_nondirectories(int n, unsigned long mask, static int unpack_failed(struct unpack_trees_options *o, const char *message) { discard_index(&o->result); - if (!o->gently) { + if (!o->gently && !o->exiting_early) { if (message) return error("%s", message); return -1; @@ -1133,6 +1133,8 @@ return_failed: display_error_msgs(o); mark_all_ce_unused(o->src_index); ret = unpack_failed(o, NULL); + if (o->exiting_early) + ret = 0; goto done; } diff --git a/unpack-trees.h b/unpack-trees.h index 64f02cb03..799894830 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -46,6 +46,7 @@ struct unpack_trees_options { debug_unpack, skip_sparse_checkout, gently, + exiting_early, show_all_errors, dry_run; const char *prefix; diff --git a/userdiff.c b/userdiff.c index e55310cd0..01d3a8b81 100644 --- a/userdiff.c +++ b/userdiff.c @@ -281,3 +281,20 @@ struct userdiff_driver *userdiff_find_by_path(const char *path) return NULL; return userdiff_find_by_name(check.value); } + +struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver) +{ + if (!driver->textconv) + return NULL; + + if (driver->textconv_want_cache && !driver->textconv_cache) { + struct notes_cache *c = xmalloc(sizeof(*c)); + struct strbuf name = STRBUF_INIT; + + strbuf_addf(&name, "textconv/%s", driver->name); + notes_cache_init(c, name.buf, driver->textconv); + driver->textconv_cache = c; + } + + return driver; +} diff --git a/userdiff.h b/userdiff.h index 942d59495..4a7e78ffb 100644 --- a/userdiff.h +++ b/userdiff.h @@ -23,4 +23,6 @@ int userdiff_config(const char *k, const char *v); struct userdiff_driver *userdiff_find_by_name(const char *name); struct userdiff_driver *userdiff_find_by_path(const char *path); +struct userdiff_driver *userdiff_get_textconv(struct userdiff_driver *driver); + #endif /* USERDIFF */ diff --git a/wt-status.c b/wt-status.c index 9f4e0ba9c..02377729c 100644 --- a/wt-status.c +++ b/wt-status.c @@ -642,7 +642,7 @@ static void wt_status_print_other(struct wt_status *s, int i; struct strbuf buf = STRBUF_INIT; - if (!s->untracked.nr) + if (!l->nr) return; wt_status_print_other_header(s, what, how); @@ -4,58 +4,248 @@ */ #include "cache.h" -void git_inflate_init(z_streamp strm) +static const char *zerr_to_string(int status) { - const char *err; + switch (status) { + case Z_MEM_ERROR: + return "out of memory"; + case Z_VERSION_ERROR: + return "wrong version"; + case Z_NEED_DICT: + return "needs dictionary"; + case Z_DATA_ERROR: + return "data stream error"; + case Z_STREAM_ERROR: + return "stream consistency error"; + default: + return "unknown error"; + } +} - switch (inflateInit(strm)) { - case Z_OK: +/* + * avail_in and avail_out in zlib are counted in uInt, which typically + * limits the size of the buffer we can use to 4GB when interacting + * with zlib in a single call to inflate/deflate. + */ +/* #define ZLIB_BUF_MAX ((uInt)-1) */ +#define ZLIB_BUF_MAX ((uInt) 1024 * 1024 * 1024) /* 1GB */ +static inline uInt zlib_buf_cap(unsigned long len) +{ + return (ZLIB_BUF_MAX < len) ? ZLIB_BUF_MAX : len; +} + +static void zlib_pre_call(git_zstream *s) +{ + s->z.next_in = s->next_in; + s->z.next_out = s->next_out; + s->z.total_in = s->total_in; + s->z.total_out = s->total_out; + s->z.avail_in = zlib_buf_cap(s->avail_in); + s->z.avail_out = zlib_buf_cap(s->avail_out); +} + +static void zlib_post_call(git_zstream *s) +{ + unsigned long bytes_consumed; + unsigned long bytes_produced; + + bytes_consumed = s->z.next_in - s->next_in; + bytes_produced = s->z.next_out - s->next_out; + if (s->z.total_out != s->total_out + bytes_produced) + die("BUG: total_out mismatch"); + if (s->z.total_in != s->total_in + bytes_consumed) + die("BUG: total_in mismatch"); + + s->total_out = s->z.total_out; + s->total_in = s->z.total_in; + s->next_in = s->z.next_in; + s->next_out = s->z.next_out; + s->avail_in -= bytes_consumed; + s->avail_out -= bytes_produced; +} + +void git_inflate_init(git_zstream *strm) +{ + int status; + + zlib_pre_call(strm); + status = inflateInit(&strm->z); + zlib_post_call(strm); + if (status == Z_OK) return; + die("inflateInit: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); +} - case Z_MEM_ERROR: - err = "out of memory"; - break; - case Z_VERSION_ERROR: - err = "wrong version"; +void git_inflate_init_gzip_only(git_zstream *strm) +{ + /* + * Use default 15 bits, +16 is to accept only gzip and to + * yield Z_DATA_ERROR when fed zlib format. + */ + const int windowBits = 15 + 16; + int status; + + zlib_pre_call(strm); + status = inflateInit2(&strm->z, windowBits); + zlib_post_call(strm); + if (status == Z_OK) + return; + die("inflateInit2: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); +} + +void git_inflate_end(git_zstream *strm) +{ + int status; + + zlib_pre_call(strm); + status = inflateEnd(&strm->z); + zlib_post_call(strm); + if (status == Z_OK) + return; + error("inflateEnd: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); +} + +int git_inflate(git_zstream *strm, int flush) +{ + int status; + + for (;;) { + zlib_pre_call(strm); + /* Never say Z_FINISH unless we are feeding everything */ + status = inflate(&strm->z, + (strm->z.avail_in != strm->avail_in) + ? 0 : flush); + if (status == Z_MEM_ERROR) + die("inflate: out of memory"); + zlib_post_call(strm); + + /* + * Let zlib work another round, while we can still + * make progress. + */ + if ((strm->avail_out && !strm->z.avail_out) && + (status == Z_OK || status == Z_BUF_ERROR)) + continue; break; + } + + switch (status) { + /* Z_BUF_ERROR: normal, needs more space in the output buffer */ + case Z_BUF_ERROR: + case Z_OK: + case Z_STREAM_END: + return status; default: - err = "error"; + break; } - die("inflateInit: %s (%s)", err, strm->msg ? strm->msg : "no message"); + error("inflate: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); + return status; } -void git_inflate_end(z_streamp strm) +#if defined(NO_DEFLATE_BOUND) || ZLIB_VERNUM < 0x1200 +#define deflateBound(c,s) ((s) + (((s) + 7) >> 3) + (((s) + 63) >> 6) + 11) +#endif + +unsigned long git_deflate_bound(git_zstream *strm, unsigned long size) { - if (inflateEnd(strm) != Z_OK) - error("inflateEnd: %s", strm->msg ? strm->msg : "failed"); + return deflateBound(&strm->z, size); } -int git_inflate(z_streamp strm, int flush) +void git_deflate_init(git_zstream *strm, int level) { - int ret = inflate(strm, flush); - const char *err; + int status; - switch (ret) { - /* Out of memory is fatal. */ - case Z_MEM_ERROR: - die("inflate: out of memory"); + zlib_pre_call(strm); + status = deflateInit(&strm->z, level); + zlib_post_call(strm); + if (status == Z_OK) + return; + die("deflateInit: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); +} - /* Data corruption errors: we may want to recover from them (fsck) */ - case Z_NEED_DICT: - err = "needs dictionary"; break; - case Z_DATA_ERROR: - err = "data stream error"; break; - case Z_STREAM_ERROR: - err = "stream consistency error"; break; - default: - err = "unknown error"; break; +void git_deflate_init_gzip(git_zstream *strm, int level) +{ + /* + * Use default 15 bits, +16 is to generate gzip header/trailer + * instead of the zlib wrapper. + */ + const int windowBits = 15 + 16; + int status; + zlib_pre_call(strm); + status = deflateInit2(&strm->z, level, + Z_DEFLATED, windowBits, + 8, Z_DEFAULT_STRATEGY); + zlib_post_call(strm); + if (status == Z_OK) + return; + die("deflateInit2: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); +} + +void git_deflate_end(git_zstream *strm) +{ + int status; + + zlib_pre_call(strm); + status = deflateEnd(&strm->z); + zlib_post_call(strm); + if (status == Z_OK) + return; + error("deflateEnd: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); +} + +int git_deflate_end_gently(git_zstream *strm) +{ + int status; + + zlib_pre_call(strm); + status = deflateEnd(&strm->z); + zlib_post_call(strm); + return status; +} + +int git_deflate(git_zstream *strm, int flush) +{ + int status; + + for (;;) { + zlib_pre_call(strm); + + /* Never say Z_FINISH unless we are feeding everything */ + status = deflate(&strm->z, + (strm->z.avail_in != strm->avail_in) + ? 0 : flush); + if (status == Z_MEM_ERROR) + die("deflate: out of memory"); + zlib_post_call(strm); + + /* + * Let zlib work another round, while we can still + * make progress. + */ + if ((strm->avail_out && !strm->z.avail_out) && + (status == Z_OK || status == Z_BUF_ERROR)) + continue; + break; + } + + switch (status) { /* Z_BUF_ERROR: normal, needs more space in the output buffer */ case Z_BUF_ERROR: case Z_OK: case Z_STREAM_END: - return ret; + return status; + default: + break; } - error("inflate: %s (%s)", err, strm->msg ? strm->msg : "no message"); - return ret; + error("deflate: %s (%s)", zerr_to_string(status), + strm->z.msg ? strm->z.msg : "no message"); + return status; } |