diff options
269 files changed, 10125 insertions, 2214 deletions
diff --git a/.gitignore b/.gitignore index 41c0b20a7..47672b0c1 100644 --- a/.gitignore +++ b/.gitignore @@ -104,7 +104,9 @@ git-receive-pack git-reflog git-relink git-remote +git-remote-curl git-repack +git-replace git-repo-config git-request-pull git-rerere @@ -41,6 +41,7 @@ Michele Ballabio <barra_cuda@katamail.com> Nanako Shiraishi <nanako3@bluebottle.com> Nanako Shiraishi <nanako3@lavabit.com> Nguyễn Thái Ngọc Duy <pclouds@gmail.com> +<nico@fluxnic.net> <nico@cam.org> Philippe Bruhat <book@cpan.org> Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk> René Scharfe <rene.scharfe@lsrfire.ath.cx> diff --git a/Documentation/RelNotes-1.6.5.txt b/Documentation/RelNotes-1.6.5.txt new file mode 100644 index 000000000..25529f723 --- /dev/null +++ b/Documentation/RelNotes-1.6.5.txt @@ -0,0 +1,161 @@ +GIT v1.6.5 Release Notes +======================== + +In git 1.7.0, which was planned to be the release after 1.6.5, "git +push" into a branch that is currently checked out will be refused by +default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyCurrentBranch in the receiving +repository. + +Also, "git push $there :$killed" to delete the branch $killed in a remote +repository $there, when $killed branch is the current branch pointed at by +its HEAD, will be refused by default. + +You can choose what should happen upon such a push by setting the +configuration variable receive.denyDeleteCurrent in the receiving +repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://git.or.cz/gitwiki/GitFaq#non-bare + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the reason why this change is needed and the +transition plan. + +Updates since v1.6.4 +-------------------- + +(subsystems) + + * various updates to git-svn and gitweb. + +(portability) + + * more improvements on mingw port. + + * mingw will also give FRSX as the default value for the LESS + environment variable when the user does not have one. + +(performance) + + * On major platforms, the system can be compiled to use with Linus's + block-sha1 implementation of the SHA-1 hash algorithm, which + outperforms the default fallback implementation we borrowed from + Mozzilla. + + * Unnecessary inefficiency in deepening of a shallow repository has + been removed. + + * The "git" main binary used to link with libcurl, which then dragged + in a large number of external libraries. When using basic plumbing + commands in scripts, this unnecessarily slowed things down. We now + implement http/https/ftp transfer as a separate executable as we + used to. + + * "git clone" run locally hardlinks or copies the files in .git/ to + newly created repository. It used to give new mtime to copied files, + but this delayed garbage collection to trigger unnecessarily in the + cloned repository. We now preserve mtime for these files to avoid + this issue. + +(usability, bells and whistles) + + * Human writable date format to various options, e.g. --since=yesterday, + master@{2000.09.17}, are taught to infer some omitted input properly. + + * A few programs gave verbose "advice" messages to help uninitiated + people when issuing error messages. An infrastructure to allow + users to squelch them has been introduced, and a few such messages + can be silenced now. + + * refs/replace/ hierarchy is designed to be usable as a replacement + of the "grafts" mechanism, with the added advantage that it can be + transferred across repositories. + + * "git am" learned to optionally ignore whitespace differences. + + * "git am" handles input e-mail files that has CRLF line endings sensibly. + + * "git am" learned "--scissors" option to allow you to discard early part + of an incoming e-mail. + + * "git checkout", "git reset" and "git stash" learned to pick and + choose to use selected changes you made, similar to "git add -p". + + * "git clone" learned a "-b" option to pick a HEAD to check out + different from the remote's default branch. + + * "git commit --dry-run $args" is a new recommended way to ask "what would + happen if I try to commit with these arguments." + + * "git commit --dry-run" and "git status" shows conflicted paths in a + separate section to make them easier to spot during a merge. + + * "git cvsimport" now supports password-protected pserver access even + when the password is not taken from ~/.cvspass file. + + * "git fast-export" learned --no-data option that can be useful when + reordering commits and trees without touching the contents of + blobs. + + * "git fast-import" has a pair of new front-end in contrib/ area. + + * "git init" learned to mkdir/chdir into a directory when given an + extra argument (i.e. "git init this"). + + * "git instaweb" optionally can use mongoose as the web server. + + * "git log --decorate" can optionally be told with --decorate=full to + give the reference name in full. + + * "git merge" issued an unnecessarily scary message when it detected + that the merge may have to touch the path that the user has local + uncommitted changes to. The message has been reworded to make it + clear that the command aborted, without doing any harm. + + * "git push" can be told to be --quiet. + + * "git push" pays attention to url.$base.pushInsteadOf and uses a URL + that is derived from the URL used for fetching. + + * informational output from "git reset" that lists the locally modified + paths is made consistent with that of "git checkout $another_branch". + + * "git submodule" learned to give submodule name to scripts run with + "foreach" subcommand. + + * various subcommands to "git submodule" learned --recursive option. + + * "git submodule summary" learned --files option to compare the work + tree vs the commit bound at submodule path, instead of comparing + the index. + + * "git upload-pack", which is the server side support for "git clone" and + "git fetch", can call a new post-upload-pack hook for statistics purposes. + +(developers) + + * With GIT_TEST_OPTS="--root=/p/a/t/h", tests can be run outside the + source directory; using tmpfs may give faster turnaround. + + +Fixes since v1.6.4 +------------------ + +# All of the fixes in v1.6.4.X maintenance series are included in this +# release, unless otherwise noted. + +# Here are fixes that this release has, but have not been backported to +# v1.6.4.X series. + +-- +exec >/var/tmp/1 +O=v1.6.4.2-298-gdf01e7c +O=v1.6.5-rc0-49-g5f2b1e6 +echo O=$(git describe master) +git shortlog --no-merges $O..master --not maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 2632c5149..be0b8caca 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -113,6 +113,21 @@ For command-specific variables, you will find a more detailed description in the appropriate manual page. You will find a description of non-core porcelain configuration variables in the respective porcelain documentation. +advice.*:: + When set to 'true', display the given optional help message. + When set to 'false', do not display. The configuration variables + are: ++ +-- + pushNonFastForward:: + Advice shown when linkgit:git-push[1] refuses + non-fast-forward refs. Default: true. + statusHints:: + Directions on how to stage/unstage/add shown in the + output of linkgit:git-status[1] and the template shown + when writing commit messages. Default: true. +-- + core.fileMode:: If false, the executable bit differences between the index and the working copy are ignored; useful on broken filesystems like FAT. @@ -461,6 +476,14 @@ it will be treated as a shell command. For example, defining executed from the top-level directory of a repository, which may not necessarily be the current directory. +apply.ignorewhitespace:: + When set to 'change', tells 'git-apply' to ignore changes in + whitespace, in the same way as the '--ignore-space-change' + option. + When set to one of: no, none, never, false tells 'git-apply' to + respect all whitespace differences. + See linkgit:git-apply[1]. + apply.whitespace:: Tells 'git-apply' how to handle whitespaces, in the same way as the '--whitespace' option. See linkgit:git-apply[1]. @@ -1492,6 +1515,19 @@ url.<base>.insteadOf:: never-before-seen repository on the site. When more than one insteadOf strings match a given URL, the longest match is used. +url.<base>.pushInsteadOf:: + Any URL that starts with this value will not be pushed to; + instead, it will be rewritten to start with <base>, and the + resulting URL will be pushed to. In cases where some site serves + a large number of repositories, and serves them with multiple + access methods, some of which do not allow push, this feature + allows people to specify a pull-only URL and have git + automatically use an appropriate URL to push, even for a + never-before-seen repository on the site. When more than one + pushInsteadOf strings match a given URL, the longest match is + used. If a remote has an explicit pushurl, git will ignore this + setting for that remote. + user.email:: Your email address to be recorded in any newly created commits. Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index e67b7e875..45ebf87ca 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -72,9 +72,14 @@ OPTIONS -p:: --patch:: - Similar to Interactive mode but the initial command loop is - bypassed and the 'patch' subcommand is invoked using each of - the specified filepatterns before exiting. + Interactively choose hunks of patch between the index and the + work tree and add them to the index. This gives the user a chance + to review the difference before adding modified contents to the + index. + + This effectively runs ``add --interactive``, but bypasses the + initial command menu and directly jumps to `patch` subcommand. + See ``Interactive mode'' for details. -e, \--edit:: Open the diff vs. the index in an editor and let the user diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 32e689b2b..67ad5da9c 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -11,9 +11,9 @@ SYNOPSIS [verse] 'git am' [--signoff] [--keep] [--utf8 | --no-utf8] [--3way] [--interactive] [--committer-date-is-author-date] - [--ignore-date] + [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] - [--reject] [-q | --quiet] + [--reject] [-q | --quiet] [--scissors | --no-scissors] [<mbox> | <Maildir>...] 'git am' (--skip | --resolved | --abort) @@ -39,6 +39,14 @@ OPTIONS --keep:: Pass `-k` flag to 'git-mailinfo' (see linkgit:git-mailinfo[1]). +-c:: +--scissors:: + Remove everything in body before a scissors line (see + linkgit:git-mailinfo[1]). + +---no-scissors:: + Ignore scissors lines (see linkgit:git-mailinfo[1]). + -q:: --quiet:: Be quiet. Only print error messages. @@ -65,6 +73,9 @@ default. You can use `--no-utf8` to override this. it is supposed to apply to and we have those blobs available locally. +--ignore-date:: +--ignore-space-change:: +--ignore-whitespace:: --whitespace=<option>:: -C<n>:: -p<n>:: @@ -125,10 +136,8 @@ the commit, after stripping common prefix "[PATCH <anything>]". The "Subject: " line is supposed to concisely describe what the commit is about in one line of text. -"From: " and "Subject: " lines starting the body (the rest of the -message after the blank line terminating the RFC2822 headers) -override the respective commit author name and title values taken -from the headers. +"From: " and "Subject: " lines starting the body override the respective +commit author name and title values taken from the headers. The commit message is formed by the title taken from the "Subject: ", a blank line and the body of the message up to diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 735374d7d..5ee8c91f2 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -13,6 +13,7 @@ SYNOPSIS [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached] + [--ignore-space-change | --ignore-whitespace ] [--whitespace=<nowarn|warn|fix|error|error-all>] [--exclude=PATH] [--include=PATH] [--directory=<root>] [--verbose] [<patch>...] @@ -149,6 +150,14 @@ patch to each path is used. A patch to a path that does not match any include/exclude pattern is used by default if there is no include pattern on the command line, and ignored if there is any include pattern. +--ignore-space-change:: +--ignore-whitespace:: + When applying a patch, ignore changes in whitespace in context + lines if necessary. + Context lines will preserve their whitespace, and they will not + undergo whitespace fixing regardless of the value of the + `--whitespace` option. New lines will still be fixed, though. + --whitespace=<action>:: When applying a patch, detect a new or modified line that has whitespace errors. What are considered whitespace errors is @@ -205,6 +214,10 @@ running `git apply --directory=modules/git-gui`. Configuration ------------- +apply.ignorewhitespace:: + Set to 'change' if you want changes in whitespace to be ignored by default. + Set to one of: no, none, never, false if you want changes in + whitespace to be significant. apply.whitespace:: When no `--whitespace` flag is given from the command line, this configuration item is used as the default. diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 92444ddf1..3d1c1e75b 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>] - [--output=<file>] [--worktree-attributes] + [-o | --output=<file>] [--worktree-attributes] [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish> [path...] @@ -34,8 +34,11 @@ OPTIONS ------- --format=<fmt>:: - Format of the resulting archive: 'tar' or 'zip'. The default - is 'tar'. + Format of the resulting archive: 'tar' or 'zip'. If this option + is not given, and the output file is specified, the format is + inferred from the filename if possible (e.g. writing to "foo.zip" + makes the output to be in the zip format). Otherwise the output + format is `tar`. -l:: --list:: @@ -48,6 +51,7 @@ OPTIONS --prefix=<prefix>/:: Prepend <prefix>/ to each filename in the archive. +-o <file>:: --output=<file>:: Write the archive to <file> instead of stdout. @@ -129,6 +133,12 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs 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:: + + 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. + SEE ALSO -------- diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index ae201deb7..aad71dc59 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -76,6 +76,7 @@ OPTIONS based sha1 expressions such as "<branchname>@\{yesterday}". -f:: +--force:: Reset <branchname> to <startpoint> if <branchname> exists already. Without `-f` 'git-branch' refuses to change an existing branch. @@ -209,6 +210,12 @@ but different purposes: - `--no-merged` is used to find branches which are candidates for merging into HEAD, since those branches are not fully contained by HEAD. +SEE ALSO +-------- +linkgit:git-check-ref-format[1], +linkgit:git-fetch[1], +linkgit:git-remote[1]. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> and Junio C Hamano <gitster@pobox.com> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index ad4b31e89..37c1810e3 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -11,6 +11,7 @@ SYNOPSIS 'git checkout' [-q] [-f] [-m] [<branch>] 'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... +'git checkout' --patch [<tree-ish>] [--] [<paths>...] DESCRIPTION ----------- @@ -25,7 +26,7 @@ use the --track or --no-track options, which will be passed to `git branch`. As a convenience, --track without `-b` implies branch creation; see the description of --track below. -When <paths> are given, this command does *not* switch +When <paths> or --patch are given, this command does *not* switch branches. It updates the named paths in the working tree from the index file, or from a named <tree-ish> (most often a commit). In this case, the `-b` and `--track` options are meaningless and giving @@ -45,9 +46,11 @@ file can be discarded to recreate the original conflicted merge result. OPTIONS ------- -q:: +--quiet:: Quiet, suppress feedback messages. -f:: +--force:: When switching branches, proceed even if the index or the working tree differs from HEAD. This is used to throw away local changes. @@ -113,6 +116,16 @@ the conflicted merge in the specified paths. "merge" (default) and "diff3" (in addition to what is shown by "merge" style, shows the original contents). +-p:: +--patch:: + Interactively select hunks in the difference between the + <tree-ish> (or the index, if unspecified) and the working + tree. The chosen hunks are then applied in reverse to the + working tree (and if a <tree-ish> was specified, the index). ++ +This means that you can use `git checkout -p` to selectively discard +edits from your current working tree. + <branch>:: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index ae8938b2d..9d291bdd2 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -32,6 +32,7 @@ OPTIONS if you really want to remove such a directory. -f:: +--force:: If the git configuration specifies clean.requireForce as true, 'git-clean' will refuse to run unless given -f or -n. diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 1709a2dbd..aacf4fd32 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git clone' [--template=<template_directory>] [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror] [-o <name>] [-u <upload-pack>] [--reference <repository>] - [--depth <depth>] [--] <repository> [<directory>] + [--depth <depth>] [--recursive] [--] <repository> [<directory>] DESCRIPTION ----------- @@ -127,6 +127,13 @@ objects from the source repository into a pack in the cloned repository. Instead of using the remote name 'origin' to keep track of the upstream repository, use <name>. +--branch <name>:: +-b <name>:: + Instead of pointing the newly created HEAD to the branch pointed + to by the cloned repository's HEAD, point to <name> branch + instead. In a non-bare repository, this is the branch that will + be checked out. + --upload-pack <upload-pack>:: -u <upload-pack>:: When given, and the repository to clone from is accessed @@ -147,6 +154,14 @@ objects from the source repository into a pack in the cloned repository. with a long history, and would want to send in fixes as patches. +--recursive:: + After the clone is created, initialize all submodules within, + using their default settings. This is equivalent to running + 'git submodule update --init --recursive' immediately after + the clone is finished. This option is ignored if the cloned + repository does not have a worktree/checkout (i.e. if any of + `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) + <repository>:: The (possibly remote) repository to clone from. See the <<URLS,URLS>> section below for more information on specifying diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index b5d81be7e..0578a40d8 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -8,7 +8,7 @@ git-commit - Record changes to the repository SYNOPSIS -------- [verse] -'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] +'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] [(-c | -C) <commit>] [-F <file> | -m <msg>] [--allow-empty] [--no-verify] [-e] [--author=<author>] [--cleanup=<mode>] [--] [[-i | -o ]<file>...] @@ -42,10 +42,9 @@ The content to be added can be specified in several ways: by one which files should be part of the commit, before finalizing the operation. Currently, this is done by invoking 'git-add --interactive'. -The 'git-status' command can be used to obtain a +The `--dry-run` option can be used to obtain a summary of what is included by any of the above for the next -commit by giving the same set of parameters you would give to -this command. +commit by giving the same set of parameters (options and paths). If you make a commit and then find a mistake immediately after that, you can recover from it with 'git-reset'. @@ -198,6 +197,11 @@ specified. --quiet:: Suppress commit summary message. +--dry-run:: + Do not create a commit, but show a list of paths that are + to be committed, paths with local changes that will be left + uncommitted and paths that are untracked. + \--:: Do not interpret any more arguments as options. diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index af2328d40..75b06f33e 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -82,6 +82,14 @@ marks the same across runs. allow that. So fake a tagger to be able to fast-import the output. +--no-data:: + Skip output of blob objects and instead refer to blobs via + their original SHA-1 hash. This is useful when rewriting the + directory structure or history of a repository without + touching the contents of individual files. Note that the + resulting stream can only be used by a repository which + already contains the necessary objects. + [git-rev-list-args...]:: A list of arguments, acceptable to 'git-rev-parse' and 'git-rev-list', that specifies the specific objects and references diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index b753c9d76..8c700200f 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -17,6 +17,7 @@ SYNOPSIS [-l | --files-with-matches] [-L | --files-without-match] [-z | --null] [-c | --count] [--all-match] + [--max-depth <depth>] [--color | --no-color] [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> @@ -47,6 +48,10 @@ OPTIONS -I:: Don't match the pattern in binary files. +--max-depth <depth>:: + For each pathspec given on command line, descend at most <depth> + levels of directories. A negative value means no limit. + -w:: --word-regexp:: Match the pattern only at word boundary (either begin at the diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 1fd0ff261..eba3cb499 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -8,7 +8,7 @@ git-init-db - Creates an empty git repository SYNOPSIS -------- -'git init-db' [-q | --quiet] [--template=<template_directory>] [--shared[=<permissions>]] +'git init-db' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] DESCRIPTION diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 7151d12f3..f081b24d9 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -8,7 +8,7 @@ git-init - Create an empty git repository or reinitialize an existing one SYNOPSIS -------- -'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] +'git init' [-q | --quiet] [--bare] [--template=<template_directory>] [--shared[=<permissions>]] [directory] OPTIONS @@ -74,6 +74,9 @@ By default, the configuration flag receive.denyNonFastForwards is enabled in shared repositories, so that you cannot force a non fast-forwarding push into it. +If you name a (possibly non-existent) directory at the end of the command +line, the command is run inside the directory (possibly after creating it). + -- diff --git a/Documentation/git-instaweb.txt b/Documentation/git-instaweb.txt index 22da21a54..0771f2544 100644 --- a/Documentation/git-instaweb.txt +++ b/Documentation/git-instaweb.txt @@ -29,7 +29,7 @@ OPTIONS The HTTP daemon command-line that will be executed. Command-line options may be specified here, and the configuration file will be added at the end of the command-line. - Currently lighttpd, apache2 and webrick are supported. + Currently apache2, lighttpd, mongoose and webrick are supported. (Default: lighttpd) -m:: diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 057a021eb..021066e95 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -44,7 +44,7 @@ OPTIONS -o:: --others:: - Show other files in the output + Show other (i.e. untracked) files in the output -i:: --ignored:: diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 8d95aaa30..996c3fcc6 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -8,7 +8,7 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message SYNOPSIS -------- -'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] <msg> <patch> +'git mailinfo' [-k] [-u | --encoding=<encoding> | -n] [--scissors] <msg> <patch> DESCRIPTION @@ -49,6 +49,25 @@ conversion, even with this flag. -n:: Disable all charset re-coding of the metadata. +--scissors:: + Remove everything in body before a scissors line. A line that + mainly consists of scissors (either ">8" or "8<") and perforation + (dash "-") marks is called a scissors line, and is used to request + the reader to cut the message at that line. If such a line + appears in the body of the message before the patch, everything + before it (including the scissors line itself) is ignored when + this option is used. ++ +This is useful if you want to begin your message in a discussion thread +with comments and suggestions on the message you are responding to, and to +conclude it with a patch submission, separating the discussion and the +beginning of the proposed commit log message with a scissors line. ++ +This can enabled by default with the configuration option mailinfo.scissors. + +--no-scissors:: + Ignore scissors lines. Useful for overriding mailinfo.scissors settings. + <msg>:: The commit log message extracted from e-mail, usually except the title line which comes from e-mail Subject. diff --git a/Documentation/git-merge-base.txt b/Documentation/git-merge-base.txt index 767486c77..ce5b36998 100644 --- a/Documentation/git-merge-base.txt +++ b/Documentation/git-merge-base.txt @@ -8,12 +8,12 @@ git-merge-base - Find as good common ancestors as possible for a merge SYNOPSIS -------- -'git merge-base' [--all] <commit> <commit>... +'git merge-base' [-a|--all] <commit> <commit>... DESCRIPTION ----------- -'git-merge-base' finds best common ancestor(s) between two commits to use +'git merge-base' finds best common ancestor(s) between two commits to use in a three-way merge. One common ancestor is 'better' than another common ancestor if the latter is an ancestor of the former. A common ancestor that does not have any better common ancestor is a 'best common @@ -27,8 +27,13 @@ commits on the command line. As the most common special case, specifying only two commits on the command line means computing the merge base between the given two commits. +As a consequence, the 'merge base' is not necessarily contained in each of the +commit arguments if more than two commits are specified. This is different +from linkgit:git-show-branch[1] when used with the `--merge-base` option. + OPTIONS ------- +-a:: --all:: Output all merge bases for the commits, instead of just one. diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt index 9c5660275..bdcb58526 100644 --- a/Documentation/git-mv.txt +++ b/Documentation/git-mv.txt @@ -28,6 +28,7 @@ committed. OPTIONS ------- -f:: +--force:: Force renaming or moving of a file even if the target exists -k:: Skip move or rename actions which would lead to an error diff --git a/Documentation/git-prune-packed.txt b/Documentation/git-prune-packed.txt index b5f26cee1..abfc6b6ea 100644 --- a/Documentation/git-prune-packed.txt +++ b/Documentation/git-prune-packed.txt @@ -8,7 +8,7 @@ git-prune-packed - Remove extra objects that are already in pack files SYNOPSIS -------- -'git prune-packed' [-n] [-q] +'git prune-packed' [-n|--dry-run] [-q|--quiet] DESCRIPTION @@ -28,10 +28,12 @@ disk storage, etc. OPTIONS ------- -n:: +--dry-run:: Don't actually remove any objects, only show those that would have been removed. -q:: +--quiet:: Squelch the progress indicator. Author diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 58d2bd5d4..ba6a8a2fb 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git push' [--all | --mirror | --tags] [--dry-run] [--receive-pack=<git-receive-pack>] +'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v | --verbose] [<repository> <refspec>...] @@ -82,6 +82,7 @@ nor in any Push line of the corresponding remotes file---see below). if the configuration option `remote.<remote>.mirror` is set. +-n:: --dry-run:: Do everything except actually send the updates. diff --git a/Documentation/git-quiltimport.txt b/Documentation/git-quiltimport.txt index d4037de51..579e8d2f3 100644 --- a/Documentation/git-quiltimport.txt +++ b/Documentation/git-quiltimport.txt @@ -9,7 +9,7 @@ git-quiltimport - Applies a quilt patchset onto the current branch SYNOPSIS -------- [verse] -'git quiltimport' [--dry-run] [--author <author>] [--patches <dir>] +'git quiltimport' [--dry-run | -n] [--author <author>] [--patches <dir>] DESCRIPTION diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 7160fa153..4a932b08c 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,10 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git read-tree' (<tree-ish> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git read-tree' [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] + [-u [--exclude-per-directory=<gitignore>] | -i]] + [--index-output=<file>] + <tree-ish1> [<tree-ish2> [<tree-ish3>]] DESCRIPTION diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index db1b71d24..0aefc34d0 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -268,8 +268,9 @@ OPTIONS exit with the message "Current branch is up to date" in such a situation. +--ignore-whitespace:: --whitespace=<option>:: - This flag is passed to the 'git-apply' program + These flag are passed to the 'git-apply' program (see linkgit:git-apply[1]) that applies the patch. Incompatible with the --interactive option. diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt new file mode 100644 index 000000000..173ee232f --- /dev/null +++ b/Documentation/git-remote-helpers.txt @@ -0,0 +1,71 @@ +git-remote-helpers(1) +===================== + +NAME +---- +git-remote-helpers - Helper programs for interoperation with remote git + +SYNOPSIS +-------- +'git remote-<transport>' <remote> + +DESCRIPTION +----------- + +These programs are normally not used directly by end users, but are +invoked by various git programs that interact with remote repositories +when the repository they would operate on will be accessed using +transport code not linked into the main git binary. Various particular +helper programs will behave as documented here. + +COMMANDS +-------- + +Commands are given by the caller on the helper's standard input, one per line. + +'capabilities':: + Lists the capabilities of the helper, one per line, ending + with a blank line. + +'list':: + Lists the refs, one per line, in the format "<value> <name> + [<attr> ...]". The value may be a hex sha1 hash, "@<dest>" for + a symref, or "?" to indicate that the helper could not get the + value of the ref. A space-separated list of attributes follows + the name; unrecognized attributes are ignored. After the + complete list, outputs a blank line. + +'fetch' <sha1> <name>:: + Fetches the given object, writing the necessary objects to the + database. Outputs a blank line when the fetch is + complete. Only objects which were reported in the ref list + with a sha1 may be fetched this way. ++ +Supported if the helper has the "fetch" capability. + +If a fatal error occurs, the program writes the error message to +stderr and exits. The caller should expect that a suitable error +message has been printed if the child closes the connection without +completing a valid response for the current command. + +Additional commands may be supported, as may be determined from +capabilities reported by the helper. + +CAPABILITIES +------------ + +'fetch':: + This helper supports the 'fetch' command. + +REF LIST ATTRIBUTES +------------------- + +None are defined yet, but the caller must accept any which are supplied. + +Documentation +------------- +Documentation by Daniel Barkalow. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt new file mode 100644 index 000000000..915cb77b2 --- /dev/null +++ b/Documentation/git-replace.txt @@ -0,0 +1,71 @@ +git-replace(1) +============== + +NAME +---- +git-replace - Create, list, delete refs to replace objects + +SYNOPSIS +-------- +[verse] +'git replace' [-f] <object> <replacement> +'git replace' -d <object>... +'git replace' -l [<pattern>] + +DESCRIPTION +----------- +Adds a 'replace' reference in `.git/refs/replace/` + +The name of the 'replace' reference is the SHA1 of the object that is +replaced. The content of the replace reference is the SHA1 of the +replacement object. + +Unless `-f` is given, the replace reference must not yet exist in +`.git/refs/replace/` directory. + +OPTIONS +------- +-f:: + If an existing replace ref for the same object exists, it will + be overwritten (instead of failing). + +-d:: + Delete existing replace refs for the given objects. + +-l <pattern>:: + List replace refs for objects that match the given pattern (or + all if no pattern is given). + Typing "git replace" without arguments, also lists all replace + refs. + +BUGS +---- +Comparing blobs or trees that have been replaced with those that +replace them will not work properly. And using 'git reset --hard' to +go back to a replaced commit will move the branch to the replacement +commit instead of the replaced commit. + +There may be other problems when using 'git rev-list' related to +pending objects. And of course things may break if an object of one +type is replaced by an object of another type (for example a blob +replaced by a commit). + +SEE ALSO +-------- +linkgit:git-tag[1] +linkgit:git-branch[1] + +Author +------ +Written by Christian Couder <chriscool@tuxfamily.org> and Junio C +Hamano <gitster@pobox.com>, based on 'git tag' by Kristian Hogsberg +<krh@redhat.com> and Carlos Rica <jasampler@gmail.com>. + +Documentation +-------------- +Documentation by Christian Couder <chriscool@tuxfamily.org> and the +git-list <git@vger.kernel.org>, based on 'git tag' documentation. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index abb25d1c0..469cf6dba 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>] 'git reset' [-q] [<commit>] [--] <paths>... +'git reset' --patch [<commit>] [--] [<paths>...] DESCRIPTION ----------- @@ -23,8 +24,9 @@ the undo in the history. If you want to undo a commit other than the latest on a branch, linkgit:git-revert[1] is your friend. -The second form with 'paths' is used to revert selected paths in -the index from a given commit, without moving HEAD. +The second and third forms with 'paths' and/or --patch are used to +revert selected paths in the index from a given commit, without moving +HEAD. OPTIONS @@ -50,6 +52,15 @@ OPTIONS and updates the files that are different between the named commit and the current commit in the working tree. +-p:: +--patch:: + Interactively select hunks in the difference between the index + and <commit> (defaults to HEAD). The chosen hunks are applied + in reverse to the index. ++ +This means that `git reset -p` is the opposite of `git add -p` (see +linkgit:git-add[1]). + -q:: Be quiet, only report errors. diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 974d9f527..3341d1b62 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -51,20 +51,26 @@ SYNOPSIS DESCRIPTION ----------- -Lists commit objects in reverse chronological order starting at the -given commit(s), taking ancestry relationship into account. This is -useful to produce human-readable log output. +List commits that are reachable by following the `parent` links from the +given commit(s), but exclude commits that are reachable from the one(s) +given with a '{caret}' in front of them. The output is given in reverse +chronological order by default. -Commits which are stated with a preceding '{caret}' cause listing to -stop at that point. Their parents are implied. Thus the following -command: +You can think of this as a set operation. Commits given on the command +line form a set of commits that are reachable from any of them, and then +commits reachable from any of the ones given with '{caret}' in front are +subtracted from that set. The remaining commits are what comes out in the +command's output. Various other options and paths parameters can be used +to further limit the result. + +Thus, the following command: ----------------------------------------------------------------------- $ git rev-list foo bar ^baz ----------------------------------------------------------------------- -means "list all the commits which are included in 'foo' and 'bar', but -not in 'baz'". +means "list all the commits which are reachable from 'foo' or 'bar', but +not from 'baz'". A special notation "'<commit1>'..'<commit2>'" can be used as a short-hand for "{caret}'<commit1>' '<commit2>'". For example, either of diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index d6b192b7b..767cf4d4b 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -142,8 +142,9 @@ user is prompted for a password while the input is masked for privacy. --smtp-server-port=<port>:: Specifies a port different from the default port (SMTP - servers typically listen to smtp port 25 and ssmtp port - 465); symbolic port names (e.g. "submission" instead of 465) + servers typically listen to smtp port 25, but may also listen to + submission port 587, or the common SSL smtp port 465); + symbolic port names (e.g. "submission" instead of 587) are also accepted. The port can also be set with the 'sendemail.smtpserverport' configuration variable. diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 89ec5364e..734336119 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -8,11 +8,12 @@ git-show-branch - Show branches and their commits SYNOPSIS -------- [verse] -'git show-branch' [--all] [--remotes] [--topo-order | --date-order] - [--current] [--color | --no-color] +'git show-branch' [-a|--all] [-r|--remotes] [--topo-order | --date-order] + [--current] [--color | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]... + 'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>] DESCRIPTION @@ -81,9 +82,11 @@ OPTIONS Synonym to `--more=-1` --merge-base:: - Instead of showing the commit list, just act like the - 'git-merge-base -a' command, except that it can accept - more than two heads. + Instead of showing the commit list, determine possible + merge bases for the specified commits. All merge bases + will be contained in all specified commits. This is + different from how linkgit:git-merge-base[1] handles + the case of three or more commits. --independent:: Among the <reference>s given, display only the ones that diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 1c64a02fe..3f14b727b 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -13,7 +13,7 @@ SYNOPSIS 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] -'git stash' [save [--keep-index] [-q|--quiet] [<message>]] +'git stash' [save [--patch] [-k|--[no-]keep-index] [-q|--quiet] [<message>]] 'git stash' clear 'git stash' create @@ -42,15 +42,27 @@ is also possible). OPTIONS ------- -save [--keep-index] [-q|--quiet] [<message>]:: +save [--patch] [--[no-]keep-index] [-q|--quiet] [<message>]:: Save your local modifications to a new 'stash', and run `git reset - --hard` to revert them. This is the default action when no - subcommand is given. The <message> part is optional and gives - the description along with the stashed state. + --hard` to revert them. The <message> part is optional and gives + the description along with the stashed state. For quickly making + a snapshot, you can omit _both_ "save" and <message>, but giving + only <message> does not trigger this action to prevent a misspelled + subcommand from making an unwanted stash. + If the `--keep-index` option is used, all changes already added to the index are left intact. ++ +With `--patch`, you can interactively select hunks from in the diff +between HEAD and the working tree to be stashed. The stash entry is +constructed such that its index state is the same as the index state +of your repository, and its worktree contains only the changes you +selected interactively. The selected changes are then rolled back +from your worktree. ++ +The `--patch` option implies `--keep-index`. You can use +`--no-keep-index` to override this. list [<options>]:: @@ -114,7 +126,8 @@ no conflicts. clear:: Remove all the stashed states. Note that those states will then - be subject to pruning, and may be difficult or impossible to recover. + be subject to pruning, and may be impossible to recover (see + 'Examples' below for a possible strategy). drop [-q|--quiet] [<stash>]:: @@ -217,6 +230,20 @@ $ edit/build/test remaining parts $ git commit foo -m 'Remaining parts' ---------------------------------------------------------------- +Recovering stashes that were cleared/dropped erroneously:: + +If you mistakenly drop or clear stashes, they cannot be recovered +through the normal safety mechanisms. However, you can try the +following incantation to get a list of stashes that are still in your +repository, but not reachable any more: ++ +---------------------------------------------------------------- +git fsck --unreachable | +grep commit | cut -d\ -f3 | +xargs git log --merges --no-walk --grep=WIP +---------------------------------------------------------------- + + SEE ALSO -------- linkgit:git-checkout[1], diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 7dd73ae14..5ccdd18c8 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -11,12 +11,12 @@ SYNOPSIS [verse] 'git submodule' [--quiet] add [-b branch] [--reference <repository>] [--] <repository> <path> -'git submodule' [--quiet] status [--cached] [--] [<path>...] +'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] 'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] - [--reference <repository>] [--merge] [--] [<path>...] -'git submodule' [--quiet] summary [--cached] [--summary-limit <n>] [commit] [--] [<path>...] -'git submodule' [--quiet] foreach <command> + [--reference <repository>] [--merge] [--recursive] [--] [<path>...] +'git submodule' [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] +'git submodule' [--quiet] foreach [--recursive] <command> 'git submodule' [--quiet] sync [--] [<path>...] @@ -100,6 +100,9 @@ status:: initialized and `+` if the currently checked out submodule commit does not match the SHA-1 found in the index of the containing repository. This command is the default command for 'git-submodule'. ++ +If '--recursive' is specified, this command will recurse into nested +submodules, and show their status as well. init:: Initialize the submodules, i.e. register each submodule name @@ -122,21 +125,31 @@ update:: If the submodule is not yet initialized, and you just want to use the setting as stored in .gitmodules, you can automatically initialize the submodule with the --init option. ++ +If '--recursive' is specified, this command will recurse into the +registered submodules, and update any nested submodules within. summary:: Show commit summary between the given commit (defaults to HEAD) and working tree/index. For a submodule in question, a series of commits in the submodule between the given super project commit and the - index or working tree (switched by --cached) are shown. + index or working tree (switched by --cached) are shown. If the option + --files is given, show the series of commits in the submodule between + the index of the super project and the working tree of the submodule + (this option doesn't allow to use the --cached option or to provide an + explicit commit). foreach:: Evaluates an arbitrary shell command in each checked out submodule. - The command has access to the variables $path and $sha1: + The command has access to the variables $name, $path and $sha1: + $name is the name of the relevant submodule section in .gitmodules, $path is the name of the submodule directory relative to the superproject, and $sha1 is the commit as recorded in the superproject. Any submodules defined in the superproject but not checked out are ignored by this command. Unless given --quiet, foreach prints the name of each submodule before evaluating the command. + If --recursive is given, submodules are traversed recursively (i.e. + the given shell command is evaluated in nested submodules as well). A non-zero return from the command in any submodule causes the processing to terminate. This can be overridden by adding '|| :' to the end of the command. @@ -169,6 +182,11 @@ OPTIONS commands typically use the commit found in the submodule HEAD, but with this option, the commit stored in the index is used instead. +--files:: + This option is only valid for the summary command. This command + compares the commit in the index with that in the submodule HEAD + when this option is used. + -n:: --summary-limit:: This option is only valid for the summary command. @@ -209,6 +227,12 @@ OPTIONS *NOTE*: Do *not* use this option unless you have read the note for linkgit:git-clone[1]'s --reference and --shared options carefully. +--recursive:: + This option is only valid for foreach, update and status commands. + Traverse submodules recursively. The operation is performed not + only in the submodules of the current repo, but also + in any nested submodules inside those submodules (and so on). + <path>...:: Paths to submodule(s). When specified this will restrict the command to only operate on the submodules found at the specified paths. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 22a0389f1..1812890a7 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -102,9 +102,6 @@ COMMANDS Store Git commit times in the local timezone instead of UTC. This makes 'git log' (even without --date=local) show the same times that `svn log` would in the local timezone. - ---parent;; - Fetch only from the SVN parent of the current HEAD. + This doesn't interfere with interoperating with the Subversion repository you cloned from, but if you wish for your local Git @@ -112,6 +109,9 @@ repository to be able to interoperate with someone else's local Git repository, either don't use this option or you should both use it in the same local timezone. +--parent;; + Fetch only from the SVN parent of the current HEAD. + --ignore-paths=<regex>;; This allows one to specify a Perl regular expression that will cause skipping of all matching paths from checkout from SVN. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index fa733214a..299b04f72 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -10,14 +10,15 @@ SYNOPSIS -------- [verse] 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] - <name> [<commit> | <object>] -'git tag' -d <name>... + <tagname> [<commit> | <object>] +'git tag' -d <tagname>... 'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>] -'git tag' -v <name>... +'git tag' -v <tagname>... DESCRIPTION ----------- -Adds a 'tag' reference in `.git/refs/tags/` + +Adds a tag reference in `.git/refs/tags/`. Unless `-f` is given, the tag must not yet exist in `.git/refs/tags/` directory. @@ -50,6 +51,7 @@ OPTIONS Make a GPG-signed tag, using the given key -f:: +--force:: Replace an existing tag with the given name (instead of failing) -d:: @@ -85,6 +87,12 @@ OPTIONS Implies `-a` if none of `-a`, `-s`, or `-u <key-id>` is given. +<tagname>:: + The name of the tag to create, delete, or describe. + The new tag name must pass all checks defined by + linkgit:git-check-ref-format[1]. Some of these checks + may restrict the characters allowed in a tag name. + CONFIGURATION ------------- By default, 'git-tag' in sign-with-default mode (-s) will use your @@ -249,6 +257,10 @@ $ GIT_COMMITTER_DATE="2006-10-02 10:31" git tag -s v1.0.1 ------------ +SEE ALSO +-------- +linkgit:git-check-ref-format[1]. + Author ------ Written by Linus Torvalds <torvalds@osdl.org>, diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt index b8e49dce4..63f3b5c74 100644 --- a/Documentation/git-upload-pack.txt +++ b/Documentation/git-upload-pack.txt @@ -20,6 +20,8 @@ The UI for the protocol is on the 'git-fetch-pack' side, and the program pair is meant to be used to pull updates from a remote repository. For push operations, see 'git-send-pack'. +After finishing the operation successfully, `post-upload-pack` +hook is called (see linkgit:githooks[5]). OPTIONS ------- diff --git a/Documentation/git-verify-pack.txt b/Documentation/git-verify-pack.txt index c8611632d..97f7f9165 100644 --- a/Documentation/git-verify-pack.txt +++ b/Documentation/git-verify-pack.txt @@ -8,7 +8,7 @@ git-verify-pack - Validate packed git archive files SYNOPSIS -------- -'git verify-pack' [-v] [--] <pack>.idx ... +'git verify-pack' [-v|--verbose] [--] <pack>.idx ... DESCRIPTION @@ -23,8 +23,15 @@ OPTIONS The idx files to verify. -v:: +--verbose:: After verifying the pack, show list of objects contained - in the pack. + in the pack and a histogram of delta chain length. + +-s:: +--stat-only:: + Do not verify the pack contents; only show the histogram of delta + chain length. With `--verbose`, list of objects is also shown. + \--:: Do not interpret any more arguments as options. diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index acc408d7e..06e0f315c 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -245,7 +245,7 @@ Both standard output and standard error output are forwarded to for the user. The default 'update' hook, when enabled--and with -`hooks.allowunannotated` config option turned on--prevents +`hooks.allowunannotated` config option unset or set to false--prevents unannotated tags to be pushed. [[post-receive]] @@ -310,6 +310,35 @@ Both standard output and standard error output are forwarded to 'git-send-pack' on the other end, so you can simply `echo` messages for the user. +post-upload-pack +---------------- + +After upload-pack successfully finishes its operation, this hook is called +for logging purposes. + +The hook is passed various pieces of information, one per line, from its +standard input. Currently the following items can be fed to the hook, but +more types of information may be added in the future: + +want SHA-1:: + 40-byte hexadecimal object name the client asked to include in the + resulting pack. Can occur one or more times in the input. + +have SHA-1:: + 40-byte hexadecimal object name the client asked to exclude from + the resulting pack, claiming to have them already. Can occur zero + or more times in the input. + +time float:: + Number of seconds spent for creating the packfile. + +size decimal:: + Size of the resulting packfile in bytes. + +kind string: + Either "clone" (when the client did not give us any "have", and asked + for all our refs with "want"), or "fetch" (otherwise). + pre-auto-gc ~~~~~~~~~~~ diff --git a/Documentation/pt_BR/gittutorial.txt b/Documentation/pt_BR/gittutorial.txt new file mode 100644 index 000000000..81e7ad7df --- /dev/null +++ b/Documentation/pt_BR/gittutorial.txt @@ -0,0 +1,675 @@ +gittutorial(7) +============== + +NAME +---- +gittutorial - Um tutorial de introdução ao git (para versão 1.5.1 ou mais nova) + +SYNOPSIS +-------- +git * + +DESCRIPTION +----------- + +Este tutorial explica como importar um novo projeto para o git, +adicionar mudanças a ele, e compartilhar mudanças com outros +desenvolvedores. + +Se, ao invés disso, você está interessado primariamente em usar git para +obter um projeto, por exemplo, para testar a última versão, você pode +preferir começar com os primeiros dois capítulos de +link:user-manual.html[O Manual do Usuário Git]. + +Primeiro, note que você pode obter documentação para um comando como +`git log --graph` com: + +------------------------------------------------ +$ man git-log +------------------------------------------------ + +ou: + +------------------------------------------------ +$ git help log +------------------------------------------------ + +Com a última forma, você pode usar o visualizador de manual de sua +escolha; veja linkgit:git-help[1] para maior informação. + +É uma boa idéia informar ao git seu nome e endereço público de email +antes de fazer qualquer operação. A maneira mais fácil de fazê-lo é: + +------------------------------------------------ +$ git config --global user.name "Seu Nome Vem Aqui" +$ git config --global user.email voce@seudominio.exemplo.com +------------------------------------------------ + + +Importando um novo projeto +----------------------- + +Assuma que você tem um tarball project.tar.gz com seu trabalho inicial. +Você pode colocá-lo sob controle de revisão git da seguinte forma: + +------------------------------------------------ +$ tar xzf project.tar.gz +$ cd project +$ git init +------------------------------------------------ + +Git irá responder + +------------------------------------------------ +Initialized empty Git repository in .git/ +------------------------------------------------ + +Você agora iniciou seu diretório de trabalho--você deve ter notado um +novo diretório criado, com o nome de ".git". + +A seguir, diga ao git para gravar um instantâneo do conteúdo de todos os +arquivos sob o diretório corrente (note o '.'), com 'git-add': + +------------------------------------------------ +$ git add . +------------------------------------------------ + +Este instantâneo está agora armazenado em uma área temporária que o git +chama de "index" ou índice. Você pode armazenar permanentemente o +conteúdo do índice no repositório com 'git-commit': + +------------------------------------------------ +$ git commit +------------------------------------------------ + +Isto vai te pedir por uma mensagem de commit. Você agora gravou sua +primeira versão de seu projeto no git. + +Fazendo mudanças +-------------- + +Modifique alguns arquivos, e, então, adicione seu conteúdo atualizado ao +índice: + +------------------------------------------------ +$ git add file1 file2 file3 +------------------------------------------------ + +Você está agora pronto para fazer o commit. Você pode ver o que está +para ser gravado usando 'git-diff' com a opção --cached: + +------------------------------------------------ +$ git diff --cached +------------------------------------------------ + +(Sem --cached, o comando 'git-diff' irá te mostrar quaisquer mudanças +que você tenha feito mas ainda não adicionou ao índice.) Você também +pode obter um breve sumário da situação com 'git-status': + +------------------------------------------------ +$ git status +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# modified: file1 +# modified: file2 +# modified: file3 +# +------------------------------------------------ + +Se você precisar fazer qualquer outro ajuste, faça-o agora, e, então, +adicione qualquer conteúdo modificado ao índice. Finalmente, grave suas +mudanças com: + +------------------------------------------------ +$ git commit +------------------------------------------------ + +Isto irá novamente te pedir por uma mensagem descrevendo a mudança, e, +então, gravar a nova versão do projeto. + +Alternativamente, ao invés de executar 'git-add' antes, você pode usar + +------------------------------------------------ +$ git commit -a +------------------------------------------------ + +o que irá automaticamente notar quaisquer arquivos modificados (mas não +novos), adicioná-los ao índices, e gravar, tudo em um único passo. + +Uma nota em mensagens de commit: Apesar de não ser exigido, é uma boa +idéia começar a mensagem com uma simples e curta (menos de 50 +caracteres) linha sumarizando a mudança, seguida de uma linha em branco +e, então, uma descrição mais detalhada. Ferramentas que transformam +commits em email, por exemplo, usam a primeira linha no campo de +cabeçalho Subject: e o resto no corpo. + +Git rastreia conteúdo, não arquivos +---------------------------- + +Muitos sistemas de controle de revisão provêem um comando `add` que diz +ao sistema para começar a rastrear mudanças em um novo arquivo. O +comando `add` do git faz algo mais simples e mais poderoso: 'git-add' é +usado tanto para arquivos novos e arquivos recentemente modificados, e +em ambos os casos, ele tira o instantâneo dos arquivos dados e armazena +o conteúdo no índice, pronto para inclusão do próximo commit. + +Visualizando história do projeto +----------------------- + +Em qualquer ponto você pode visualizar a história das suas mudanças +usando + +------------------------------------------------ +$ git log +------------------------------------------------ + +Se você também quer ver a diferença completa a cada passo, use + +------------------------------------------------ +$ git log -p +------------------------------------------------ + +Geralmente, uma visão geral da mudança é útil para ter a sensação de +cada passo + +------------------------------------------------ +$ git log --stat --summary +------------------------------------------------ + +Gerenciando "branches"/ramos +----------------- + +Um simples repositório git pode manter múltiplos ramos de +desenvolvimento. Para criar um novo ramo chamado "experimental", use + +------------------------------------------------ +$ git branch experimental +------------------------------------------------ + +Se você executar agora + +------------------------------------------------ +$ git branch +------------------------------------------------ + +você vai obter uma lista de todos os ramos existentes: + +------------------------------------------------ + experimental +* master +------------------------------------------------ + +O ramo "experimental" é o que você acaba de criar, e o ramo "master" é o +ramo padrão que foi criado pra você automaticamente. O asterisco marca +o ramo em que você está atualmente; digite + +------------------------------------------------ +$ git checkout experimental +------------------------------------------------ + +para mudar para o ramo experimental. Agora edite um arquivo, grave a +mudança, e mude de volta para o ramo master: + +------------------------------------------------ +(edita arquivo) +$ git commit -a +$ git checkout master +------------------------------------------------ + +Verifique que a mudança que você fez não está mais visível, já que ela +foi feita no ramo experimental e você está de volta ao ramo master. + +Você pode fazer uma mudança diferente no ramo master: + +------------------------------------------------ +(edit file) +$ git commit -a +------------------------------------------------ + +neste ponto, os dois ramos divergiram, com diferentes mudanças feitas em +cada um. Para unificar as mudanças feitas no experimental para o +master, execute + +------------------------------------------------ +$ git merge experimental +------------------------------------------------ + +Se as mudanças não conflitarem, estará pronto. Se existirem conflitos, +marcadores serão deixados nos arquivos problemáticos exibindo o +conflito; + +------------------------------------------------ +$ git diff +------------------------------------------------ + +vai exibir isto. Após você editar os arquivos para resolver os +conflitos, + +------------------------------------------------ +$ git commit -a +------------------------------------------------ + +irá gravar o resultado da unificação. Finalmente, + +------------------------------------------------ +$ gitk +------------------------------------------------ + +vai mostrar uma bela representação gráfica da história resultante. + +Neste ponto você pode remover seu ramo experimental com + +------------------------------------------------ +$ git branch -d experimental +------------------------------------------------ + +Este comando garante que as mudanças no ramo experimental já estão no +ramo atual. + +Se você desenvolve em um ramo ideia-louca, e se arrepende, você pode +sempre remover o ramo com + +------------------------------------- +$ git branch -D ideia-louca +------------------------------------- + +Ramos são baratos e fáceis, então isto é uma boa maneira de experimentar +alguma coisa. + +Usando git para colaboração +--------------------------- + +Suponha que Alice começou um novo projeto com um repositório git em +/home/alice/project, e que Bob, que tem um diretório home na mesma +máquina, quer contribuir. + +Bob começa com: + +------------------------------------------------ +bob$ git clone /home/alice/project myrepo +------------------------------------------------ + +Isso cria um novo diretório "myrepo" contendo um clone do repositório de +Alice. O clone está no mesmo pé que o projeto original, possuindo sua +própria cópia da história do projeto original. + +Bob então faz algumas mudanças e as grava: + +------------------------------------------------ +(editar arquivos) +bob$ git commit -a +(repetir conforme necessário) +------------------------------------------------ + +Quanto está pronto, ele diz a Alice para puxar as mudanças do +repositório em /home/bob/myrepo. Ela o faz com: + +------------------------------------------------ +alice$ cd /home/alice/project +alice$ git pull /home/bob/myrepo master +------------------------------------------------ + +Isto unifica as mudanças do ramo "master" do Bob ao ramo atual de Alice. +Se Alice fez suas próprias mudanças no intervalo, ela, então, pode +precisar corrigir manualmente quaisquer conflitos. (Note que o argumento +"master" no comando acima é, de fato, desnecessário, já que é o padrão.) + +O comando "pull" executa, então, duas operações: ele obtém mudanças de +um ramo remoto, e, então, as unifica no ramo atual. + +Note que, em geral, Alice gostaria que suas mudanças locais fossem +gravadas antes de iniciar este "pull". Se o trabalho de Bob conflita +com o que Alice fez desde que suas histórias se ramificaram, Alice irá +usar seu diretório de trabalho e o índice para resolver conflitos, e +mudanças locais existentes irão interferir com o processo de resolução +de conflitos (git ainda irá realizar a obtenção mas irá se recusar a +unificar --- Alice terá que se livrar de suas mudanças locais de alguma +forma e puxar de novo quando isso acontecer). + +Alice pode espiar o que Bob fez sem unificar primeiro, usando o comando +"fetch"; isto permite Alice inspecionar o que Bob fez, usando um símbolo +especial "FETCH_HEAD", com o fim de determinar se ele tem alguma coisa +que vale puxar, assim: + +------------------------------------------------ +alice$ git fetch /home/bob/myrepo master +alice$ git log -p HEAD..FETCH_HEAD +------------------------------------------------ + +Esta operação é segura mesmo se Alice tem mudanças locais não gravadas. +A notação de intervalo "HEAD..FETCH_HEAD" significa mostrar tudo que é +alcançável de FETCH_HEAD mas exclua tudo o que é alcançável de HEAD. +Alice já sabe tudo que leva a seu estado atual (HEAD), e revisa o que Bob +tem em seu estado (FETCH_HEAD) que ela ainda não viu com esse comando. + +Se Alice quer visualizar o que Bob fez desde que suas histórias se +ramificaram, ela pode disparar o seguinte comando: + +------------------------------------------------ +$ gitk HEAD..FETCH_HEAD +------------------------------------------------ + +Isto usa a mesma notação de intervalo que vimos antes com 'git log'. + +Alice pode querer ver o que ambos fizeram desde que ramificaram. Ela +pode usar a forma com três pontos ao invés da forma com dois pontos: + +------------------------------------------------ +$ gitk HEAD...FETCH_HEAD +------------------------------------------------ + +Isto significa "mostre tudo que é alcançável de qualquer um deles, mas +exclua tudo que é alcançável a partir de ambos". + +Por favor, note que essas notações de intervalo podem ser usadas tanto +com gitk quanto com "git log". + +Após inspecionar o que Bob fez, se não há nada urgente, Alice pode +decidir continuar trabalhando sem puxar de Bob. Se a história de Bob +tem alguma coisa que Alice precisa imediatamente, Alice pode optar por +separar seu trabalho em progresso primeiro, fazer um "pull", e, então, +finalmente, retomar seu trabalho em progresso em cima da história +resultante. + +Quando você está trabalhando em um pequeno grupo unido, não é incomum +interagir com o mesmo repositório várias e várias vezes. Definindo um +repositório remoto antes de tudo, você pode fazê-lo mais facilmente: + +------------------------------------------------ +alice$ git remote add bob /home/bob/myrepo +------------------------------------------------ + +Com isso, Alice pode executar a primeira parte da operação "pull" usando +o comando 'git-fetch' sem unificar suas mudanças com seu próprio ramo, +usando: + +------------------------------------- +alice$ git fetch bob +------------------------------------- + +Diferente da forma longa, quando Alice obteve de Bob usando um +repositório remoto antes definido com 'git-remote', o que foi obtido é +armazenado em um ramo remoto, neste caso `bob/master`. Então, após isso: + +------------------------------------- +alice$ git log -p master..bob/master +------------------------------------- + +mostra uma lista de todas as mudanças que Bob fez desde que ramificou do +ramo master de Alice. + +Após examinar essas mudanças, Alice pode unificá-las em seu ramo master: + +------------------------------------- +alice$ git merge bob/master +------------------------------------- + +Esse `merge` pode também ser feito puxando de seu próprio ramo remoto, +assim: + +------------------------------------- +alice$ git pull . remotes/bob/master +------------------------------------- + +Note que 'git pull' sempre unifica ao ramo atual, independente do que +mais foi passado na linha de comando. + +Depois, Bob pode atualizar seu repositório com as últimas mudanças de +Alice, usando + +------------------------------------- +bob$ git pull +------------------------------------- + +Note que ele não precisa dar o caminho do repositório de Alice; quando +Bob clonou seu repositório, o git armazenou a localização de seu +repositório na configuração do mesmo, e essa localização é usada +para puxar: + +------------------------------------- +bob$ git config --get remote.origin.url +/home/alice/project +------------------------------------- + +(A configuração completa criada por 'git-clone' é visível usando `git +config -l`, e a página de manual linkgit:git-config[1] explica o +significado de cada opção.) + +Git também mantém uma cópia limpa do ramo master de Alice sob o nome +"origin/master": + +------------------------------------- +bob$ git branch -r + origin/master +------------------------------------- + +Se Bob decidir depois em trabalhar em um host diferente, ele ainda pode +executar clones e puxar usando o protocolo ssh: + +------------------------------------- +bob$ git clone alice.org:/home/alice/project myrepo +------------------------------------- + +Alternativamente, o git tem um protocolo nativo, ou pode usar rsync ou +http; veja linkgit:git-pull[1] para detalhes. + +Git pode também ser usado em um modo parecido com CVS, com um +repositório central para o qual vários usuários empurram modificações; +veja linkgit:git-push[1] e linkgit:gitcvs-migration[7]. + +Explorando história +----------------- + +A história no git é representada como uma série de commits +interrelacionados. Nós já vimos que o comando 'git-log' pode listar +esses commits. Note que a primeira linha de cada entrada no log também +dá o nome para o commit: + +------------------------------------- +$ git log +commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7 +Author: Junio C Hamano <junkio@cox.net> +Date: Tue May 16 17:18:22 2006 -0700 + + merge-base: Clarify the comments on post processing. +------------------------------------- + +Nós podemos dar este nome ao 'git-show' para ver os detalhes sobre este +commit. + +------------------------------------- +$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7 +------------------------------------- + +Mas há outras formas de se referir aos commits. Você pode usar qualquer +parte inicial do nome que seja longo o bastante para identificar +unicamente o commit: + +------------------------------------- +$ git show c82a22c39c # os primeiros caracteres do nome são o bastante + # usualmente +$ git show HEAD # a ponta do ramo atual +$ git show experimental # a ponta do ramo "experimental" +------------------------------------- + +Todo commit normalmente tem um commit "pai" que aponta para o estado +anterior do projeto: + +------------------------------------- +$ git show HEAD^ # para ver o pai de HEAD +$ git show HEAD^^ # para ver o avô de HEAD +$ git show HEAD~4 # para ver o trisavô de HEAD +------------------------------------- + +Note que commits de unificação podem ter mais de um pai: + +------------------------------------- +$ git show HEAD^1 # mostra o primeiro pai de HEAD (o mesmo que HEAD^) +$ git show HEAD^2 # mostra o segundo pai de HEAD +------------------------------------- + +Você também pode dar aos commits nomes à sua escolha; após executar + +------------------------------------- +$ git tag v2.5 1b2e1d63ff +------------------------------------- + +você pode se referir a 1b2e1d63ff pelo nome "v2.5". Se você pretende +compartilhar esse nome com outras pessoas (por exemplo, para identificar +uma versão de lançamento), você deveria criar um objeto "tag", e talvez +assiná-lo; veja linkgit:git-tag[1] para detalhes. + +Qualquer comando git que precise conhecer um commit pode receber +quaisquer desses nomes. Por exemplo: + +------------------------------------- +$ git diff v2.5 HEAD # compara o HEAD atual com v2.5 +$ git branch stable v2.5 # inicia um novo ramo chamado "stable" baseado + # em v2.5 +$ git reset --hard HEAD^ # reseta seu ramo atual e seu diretório de + # trabalho a seu estado em HEAD^ +------------------------------------- + +Seja cuidadoso com o último comando: além de perder quaisquer mudanças +em seu diretório de trabalho, ele também remove todos os commits +posteriores desse ramo. Se esse ramo é o único ramo contendo esses +commits, eles serão perdidos. Também, não use 'git-reset' num ramo +publicamente visível de onde outros desenvolvedores puxam, já que vai +forçar unificações desnecessárias para que outros desenvolvedores limpem +a história. Se você precisa desfazer mudanças que você empurrou, use +'git-revert' no lugar. + +O comando 'git-grep' pode buscar strings em qualquer versão de seu +projeto, então + +------------------------------------- +$ git grep "hello" v2.5 +------------------------------------- + +procura por todas as ocorrências de "hello" em v2.5. + +Se você deixar de fora o nome do commit, 'git-grep' irá procurar +quaisquer dos arquivos que ele gerencia no diretório corrente. Então + +------------------------------------- +$ git grep "hello" +------------------------------------- + +é uma forma rápida de buscar somente os arquivos que são rastreados pelo +git. + +Muitos comandos git também recebem um conjunto de commits, o que pode +ser especificado de várias formas. Aqui estão alguns exemplos com 'git-log': + +------------------------------------- +$ git log v2.5..v2.6 # commits entre v2.5 e v2.6 +$ git log v2.5.. # commits desde v2.5 +$ git log --since="2 weeks ago" # commits das últimas 2 semanas +$ git log v2.5.. Makefile # commits desde v2.5 que modificam + # Makefile +------------------------------------- + +Você também pode dar ao 'git-log' um "intervalo" de commits onde o +primeiro não é necessariamente um ancestral do segundo; por exemplo, se +as pontas dos ramos "stable" e "master" divergiram de um commit +comum algum tempo atrás, então + +------------------------------------- +$ git log stable..master +------------------------------------- + +irá listar os commits feitos no ramo "master" mas não no ramo +"stable", enquanto + +------------------------------------- +$ git log master..stable +------------------------------------- + +irá listar a lista de commits feitos no ramo "stable" mas não no ramo +"master". + +O comando 'git-log' tem uma fraqueza: ele precisa mostrar os commits em +uma lista. Quando a história tem linhas de desenvolvimento que +divergiram e então foram unificadas novamente, a ordem em que 'git-log' +apresenta essas mudanças é irrelevante. + +A maioria dos projetos com múltiplos contribuidores (como o kernel +Linux, ou o próprio git) tem unificações frequentes, e 'gitk' faz um +trabalho melhor de visualizar sua história. Por exemplo, + +------------------------------------- +$ gitk --since="2 weeks ago" drivers/ +------------------------------------- + +permite a você navegar em quaisquer commits desde as últimas duas semanas +de commits que modificaram arquivos sob o diretório "drivers". (Nota: +você pode ajustar as fontes do gitk segurando a tecla control enquanto +pressiona "-" ou "+".) + +Finalmente, a maioria dos comandos que recebem nomes de arquivo permitirão +também, opcionalmente, preceder qualquer nome de arquivo por um +commit, para especificar uma versão particular do arquivo: + +------------------------------------- +$ git diff v2.5:Makefile HEAD:Makefile.in +------------------------------------- + +Você pode usar 'git-show' para ver tal arquivo: + +------------------------------------- +$ git show v2.5:Makefile +------------------------------------- + +Próximos passos +---------- + +Este tutorial deve ser o bastante para operar controle de revisão +distribuído básico para seus projetos. No entanto, para entender +plenamente a profundidade e o poder do git você precisa entender duas +idéias simples nas quais ele se baseia: + + * A base de objetos é um sistema bem elegante usado para armazenar a + história de seu projeto--arquivos, diretórios, e commits. + + * O arquivo de índice é um cache do estado de uma árvore de diretório, + usado para criar commits, restaurar diretórios de trabalho, e + armazenar as várias árvores envolvidas em uma unificação. + +A parte dois deste tutorial explica a base de objetos, o arquivo de +índice, e algumas outras coisinhas que você vai precisar pra usar o +máximo do git. Você pode encontrá-la em linkgit:gittutorial-2[7]. + +Se você não quiser continuar com o tutorial agora nesse momento, algumas +outras digressões que podem ser interessantes neste ponto são: + + * linkgit:git-format-patch[1], linkgit:git-am[1]: Estes convertem + séries de commits em patches para email, e vice-versa, úteis para + projetos como o kernel Linux que dependem fortemente de patches + enviados por email. + + * linkgit:git-bisect[1]: Quando há uma regressão em seu projeto, uma + forma de rastrear um bug é procurando pela história para encontrar o + commit culpado. Git bisect pode ajudar a executar uma busca binária + por esse commit. Ele é inteligente o bastante para executar uma + busca próxima da ótima mesmo no caso de uma história complexa + não-linear com muitos ramos unificados. + + * link:everyday.html[GIT diariamente com 20 e tantos comandos] + + * linkgit:gitcvs-migration[7]: Git para usuários de CVS. + +VEJA TAMBÉM +-------- +linkgit:gittutorial-2[7], +linkgit:gitcvs-migration[7], +linkgit:gitcore-tutorial[7], +linkgit:gitglossary[7], +linkgit:git-help[1], +link:everyday.html[git diariamente], +link:user-manual.html[O Manual do Usuário git] + +GIT +--- +Parte da suite linkgit:git[1]. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 2efe7a40b..b26c28133 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -35,12 +35,32 @@ Functions Convenience functions that encapsulate a sequence of start_command() followed by finish_command(). The argument argv specifies the program and its arguments. The argument opt is zero - or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, or - `RUN_COMMAND_STDOUT_TO_STDERR` that correspond to the members - .no_stdin, .git_cmd, .stdout_to_stderr of `struct child_process`. + or more of the flags `RUN_COMMAND_NO_STDIN`, `RUN_GIT_CMD`, + `RUN_COMMAND_STDOUT_TO_STDERR`, or `RUN_SILENT_EXEC_FAILURE` + that correspond to the members .no_stdin, .git_cmd, + .stdout_to_stderr, .silent_exec_failure of `struct child_process`. The argument dir corresponds the member .dir. The argument env corresponds to the member .env. +The functions above do the following: + +. If a system call failed, errno is set and -1 is returned. A diagnostic + is printed. + +. If the program was not found, then -1 is returned and errno is set to + ENOENT; a diagnostic is printed only if .silent_exec_failure is 0. + +. Otherwise, the program is run. If it terminates regularly, its exit + code is returned. No diagnistic is printed, even if the exit code is + non-zero. + +. If the program terminated due to a signal, then the return value is the + signal number - 128, ie. it is negative and so indicates an unusual + condition; a diagnostic is printed. This return value can be passed to + exit(2), which will report the same code to the parent process that a + POSIX shell's $? would report for a program that died from the signal. + + `start_async`:: Run a function asynchronously. Takes a pointer to a `struct @@ -143,6 +163,11 @@ string pointers (NULL terminated) in .env: To specify a new initial working directory for the sub-process, specify it in the .dir member. +If the program cannot be found, the functions return -1 and set +errno to ENOENT. Normally, an error message is printed, but if +.silent_exec_failure is set to 1, no message is printed for this +special error condition. + * `struct async` diff --git a/Documentation/technical/api-tree-walking.txt b/Documentation/technical/api-tree-walking.txt index e3ddf9128..55b728632 100644 --- a/Documentation/technical/api-tree-walking.txt +++ b/Documentation/technical/api-tree-walking.txt @@ -1,12 +1,145 @@ tree walking API ================ -Talk about <tree-walk.h>, things like +The tree walking API is used to traverse and inspect trees. -* struct tree_desc -* init_tree_desc -* tree_entry_extract -* update_tree_entry -* get_tree_entry +Data Structures +--------------- -(JC, Linus) +`struct name_entry`:: + + An entry in a tree. Each entry has a sha1 identifier, pathname, and + mode. + +`struct tree_desc`:: + + A semi-opaque data structure used to maintain the current state of the + walk. ++ +* `buffer` is a pointer into the memory representation of the tree. It always +points at the current entry being visited. + +* `size` counts the number of bytes left in the `buffer`. + +* `entry` points to the current entry being visited. + +`struct traverse_info`:: + + A structure used to maintain the state of a traversal. ++ +* `prev` points to the traverse_info which was used to descend into the +current tree. If this is the top-level tree `prev` will point to +a dummy traverse_info. + +* `name` is the entry for the current tree (if the tree is a subtree). + +* `pathlen` is the length of the full path for the current tree. + +* `conflicts` can be used by callbacks to maintain directory-file conflicts. + +* `fn` is a callback called for each entry in the tree. See Traversing for more +information. + +* `data` can be anything the `fn` callback would want to use. + +Initializing +------------ + +`init_tree_desc`:: + + Initialize a `tree_desc` and decode its first entry. The buffer and + size parameters are assumed to be the same as the buffer and size + members of `struct tree`. + +`fill_tree_descriptor`:: + + Initialize a `tree_desc` and decode its first entry given the sha1 of + a tree. Returns the `buffer` member if the sha1 is a valid tree + identifier and NULL otherwise. + +`setup_traverse_info`:: + + Initialize a `traverse_info` given the pathname of the tree to start + traversing from. The `base` argument is assumed to be the `path` + member of the `name_entry` being recursed into unless the tree is a + top-level tree in which case the empty string ("") is used. + +Walking +------- + +`tree_entry`:: + + Visit the next entry in a tree. Returns 1 when there are more entries + left to visit and 0 when all entries have been visited. This is + commonly used in the test of a while loop. + +`tree_entry_len`:: + + Calculate the length of a tree entry's pathname. This utilizes the + memory structure of a tree entry to avoid the overhead of using a + generic strlen(). + +`update_tree_entry`:: + + Walk to the next entry in a tree. This is commonly used in conjunction + with `tree_entry_extract` to inspect the current entry. + +`tree_entry_extract`:: + + Decode the entry currently being visited (the one pointed to by + `tree_desc's` `entry` member) and return the sha1 of the entry. The + `pathp` and `modep` arguments are set to the entry's pathname and mode + respectively. + +`get_tree_entry`:: + + Find an entry in a tree given a pathname and the sha1 of a tree to + search. Returns 0 if the entry is found and -1 otherwise. The third + and fourth parameters are set to the entry's sha1 and mode + respectively. + +Traversing +---------- + +`traverse_trees`:: + + Traverse `n` number of trees in parallel. The `fn` callback member of + `traverse_info` is called once for each tree entry. + +`traverse_callback_t`:: + The arguments passed to the traverse callback are as follows: ++ +* `n` counts the number of trees being traversed. + +* `mask` has its nth bit set if something exists in the nth entry. + +* `dirmask` has its nth bit set if the nth tree's entry is a directory. + +* `entry` is an array of size `n` where the nth entry is from the nth tree. + +* `info` maintains the state of the traversal. + ++ +Returning a negative value will terminate the traversal. Otherwise the +return value is treated as an update mask. If the nth bit is set the nth tree +will be updated and if the bit is not set the nth tree entry will be the +same in the next callback invocation. + +`make_traverse_path`:: + + Generate the full pathname of a tree entry based from the root of the + traversal. For example, if the traversal has recursed into another + tree named "bar" the pathname of an entry "baz" in the "bar" + tree would be "bar/baz". + +`traverse_path_len`:: + + Calculate the length of a pathname returned by `make_traverse_path`. + This utilizes the memory structure of a tree entry to avoid the + overhead of using a generic strlen(). + +Authors +------- + +Written by Junio C Hamano <gitster@pobox.com> and Linus Torvalds +<torvalds@linux-foundation.org> diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 5355ebc0f..d813ceb72 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -67,3 +67,21 @@ For example, with this: a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be rewritten in any context that takes a URL to be "git://git.host.xz/repo.git". +If you want to rewrite URLs for push only, you can create a +configuration section of the form: + +------------ + [url "<actual url base>"] + pushInsteadOf = <other url base> +------------ + +For example, with this: + +------------ + [url "ssh://example.org/"] + pushInsteadOf = git://example.org/ +------------ + +a URL like "git://example.org/path/to/repo.git" will be rewritten to +"ssh://example.org/path/to/repo.git" for pushes, but pulls will still +use the original URL. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 68b95733c..d7d9a9a06 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.4.4 +DEF_VER=v1.6.4.GIT LF=' ' @@ -13,6 +13,10 @@ that uses $prefix, the built results have some paths encoded, which are derived from $prefix, so "make all; make prefix=/usr install" would not work. +The beginning of the Makefile documents many variables that affect the way +git is built. You can override them either from the command line, or in a +config.mak file. + Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead @@ -48,32 +52,42 @@ Issues of note: export GIT_EXEC_PATH PATH GITPERLLIB - Git is reasonably self-sufficient, but does depend on a few external - programs and libraries: + programs and libraries. Git can be used without most of them by adding + the approriate "NO_<LIBRARY>=YesPlease" to the make command line or + config.mak file. - "zlib", the compression library. Git won't build without it. - - "openssl". Unless you specify otherwise, you'll get the SHA1 - library from here. + - "ssh" is used to push and pull over the net. - If you don't have openssl, you can use one of the SHA1 libraries - that come with git (git includes the one from Mozilla, and has - its own PowerPC and ARM optimized ones too - see the Makefile). + - A POSIX-compliant shell is required to run many scripts needed + for everyday use (e.g. "bisect", "pull"). - - libcurl library; git-http-fetch and git-fetch use them. You - might also want the "curl" executable for debugging purposes. - If you do not use http transfer, you are probably OK if you - do not have them. + - "Perl" is needed to use some of the features (e.g. preparing a + partial commit using "git add -i/-p", interacting with svn + repositories with "git svn"). If you can live without these, use + NO_PERL. - - expat library; git-http-push uses it for remote lock - management over DAV. Similar to "curl" above, this is optional. + - "openssl" library is used by git-imap-send to use IMAP over SSL. + If you don't need it, use NO_OPENSSL. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the - history graphically, and in git-gui. + By default, git uses OpenSSL for SHA1 but it will use it's own + library (inspired by Mozilla's) with either NO_OPENSSL or + BLK_SHA1. Also included is a version optimized for PowerPC + (PPC_SHA1). + + - "libcurl" library is used by git-http-fetch and git-fetch. You + might also want the "curl" executable for debugging purposes. + If you do not use http:// or https:// repositories, you do not + have to have them (use NO_CURL). - - "ssh" is used to push and pull over the net + - "expat" library; git-http-push uses it for remote lock + management over DAV. Similar to "curl" above, this is optional + (with NO_EXPAT). - - "perl" and POSIX-compliant shells are needed to use most of - the bare-bones Porcelainish scripts. + - "wish", the Tcl/Tk windowing shell is used in gitk to show the + history graphically, and in git-gui. If you don't want gitk or + git-gui, you can use NO_TCLTK. - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not @@ -16,7 +16,7 @@ all:: # when attempting to read from an fopen'ed directory. # # Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies MOZILLA_SHA1. +# This also implies BLK_SHA1. # # Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// @@ -84,18 +84,16 @@ all:: # specify your own (or DarwinPort's) include directories and # library directories by defining CFLAGS and LDFLAGS appropriately. # +# Define BLK_SHA1 environment variable if you want the C version +# of the SHA1 that assumes you can do unaligned 32-bit loads and +# have a fast htonl() function. +# # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # -# Define ARM_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for ARM. -# -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. +# Define NEEDS_CRYPTO_WITH_SSL if you need -lcrypto when using -lssl (Darwin). # -# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). +# Define NEEDS_SSL_WITH_CRYPTO if you need -lssl when using -lcrypto (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). # @@ -361,7 +359,6 @@ PROGRAMS += git-patch-id$X PROGRAMS += git-shell$X PROGRAMS += git-show-index$X PROGRAMS += git-unpack-file$X -PROGRAMS += git-update-server-info$X PROGRAMS += git-upload-pack$X PROGRAMS += git-var$X @@ -383,7 +380,8 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-whatchanged$X -# what 'all' will build and 'install' will install, in gitexecdir +# what 'all' will build and 'install' will install in gitexecdir, +# excluding programs for built-in commands ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir @@ -402,6 +400,7 @@ export PERL_PATH LIB_FILE=libgit.a XDIFF_LIB=xdiff/lib.a +LIB_H += advice.h LIB_H += archive.h LIB_H += attr.h LIB_H += blob.h @@ -459,6 +458,7 @@ LIB_H += utf8.h LIB_H += wt-status.h LIB_OBJS += abspath.o +LIB_OBJS += advice.o LIB_OBJS += alias.o LIB_OBJS += alloc.o LIB_OBJS += archive.o @@ -532,6 +532,7 @@ LIB_OBJS += read-cache.o LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += remote.o +LIB_OBJS += replace_object.o LIB_OBJS += rerere.o LIB_OBJS += revision.o LIB_OBJS += run-command.o @@ -549,6 +550,7 @@ LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o LIB_OBJS += transport.o +LIB_OBJS += transport-helper.o LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o @@ -621,6 +623,7 @@ BUILTIN_OBJS += builtin-read-tree.o BUILTIN_OBJS += builtin-receive-pack.o BUILTIN_OBJS += builtin-reflog.o BUILTIN_OBJS += builtin-remote.o +BUILTIN_OBJS += builtin-replace.o BUILTIN_OBJS += builtin-rerere.o BUILTIN_OBJS += builtin-reset.o BUILTIN_OBJS += builtin-rev-list.o @@ -638,6 +641,7 @@ BUILTIN_OBJS += builtin-tar-tree.o BUILTIN_OBJS += builtin-unpack-objects.o BUILTIN_OBJS += builtin-update-index.o BUILTIN_OBJS += builtin-update-ref.o +BUILTIN_OBJS += builtin-update-server-info.o BUILTIN_OBJS += builtin-upload-archive.o BUILTIN_OBJS += builtin-verify-pack.o BUILTIN_OBJS += builtin-verify-tag.o @@ -706,6 +710,7 @@ ifeq ($(uname_S),SCO_SV) TAR = gtar endif ifeq ($(uname_S),Darwin) + NEEDS_CRYPTO_WITH_SSL = YesPlease NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) @@ -751,9 +756,6 @@ ifeq ($(uname_S),SunOS) NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease endif - ifdef NO_IPV6 - NEEDS_RESOLV = YesPlease - endif INSTALL = /usr/ucb/install TAR = gtar BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ -DHAVE_ALLOCA_H @@ -922,10 +924,6 @@ else NO_PTHREADS = YesPlease endif endif -ifneq (,$(findstring arm,$(uname_M))) - ARM_SHA1 = YesPlease - NO_MKSTEMPS = YesPlease -endif -include config.mak.autogen -include config.mak @@ -979,9 +977,7 @@ else else CURL_LIBCURL = -lcurl endif - BUILTIN_OBJS += builtin-http-fetch.o - EXTLIBS += $(CURL_LIBCURL) - LIB_OBJS += http.o http-walker.o + PROGRAMS += git-remote-curl$X git-http-fetch$X curl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p) ifeq "$(curl_check)" "070908" ifndef NO_EXPAT @@ -1016,9 +1012,12 @@ ifndef NO_OPENSSL else OPENSSL_LINK = endif + ifdef NEEDS_CRYPTO_WITH_SSL + OPENSSL_LINK += -lcrypto + endif else BASIC_CFLAGS += -DNO_OPENSSL - MOZILLA_SHA1 = 1 + BLK_SHA1 = 1 OPENSSL_LIBSSL = endif ifdef NEEDS_SSL_WITH_CRYPTO @@ -1167,23 +1166,18 @@ ifdef NO_DEFLATE_BOUND BASIC_CFLAGS += -DNO_DEFLATE_BOUND endif +ifdef BLK_SHA1 + SHA1_HEADER = "block-sha1/sha1.h" + LIB_OBJS += block-sha1/sha1.o +else ifdef PPC_SHA1 SHA1_HEADER = "ppc/sha1.h" LIB_OBJS += ppc/sha1.o ppc/sha1ppc.o else -ifdef ARM_SHA1 - SHA1_HEADER = "arm/sha1.h" - LIB_OBJS += arm/sha1.o arm/sha1_arm.o -else -ifdef MOZILLA_SHA1 - SHA1_HEADER = "mozilla-sha1/sha1.h" - LIB_OBJS += mozilla-sha1/sha1.o -else SHA1_HEADER = <openssl/sha.h> EXTLIBS += $(LIB_4_CRYPTO) endif endif -endif ifdef NO_PERL_MAKEMAKER export NO_PERL_MAKEMAKER endif @@ -1257,6 +1251,7 @@ ifndef V QUIET_LINK = @echo ' ' LINK $@; QUIET_BUILT_IN = @echo ' ' BUILTIN $@; QUIET_GEN = @echo ' ' GEN $@; + QUIET_LNCP = @echo ' ' LN/CP $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir @@ -1484,12 +1479,21 @@ git-imap-send$X: imap-send.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) -http.o http-walker.o http-push.o transport.o: http.h +http.o http-walker.o http-push.o: http.h + +http.o http-walker.o: $(LIB_H) +git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(CURL_LIBCURL) git-http-push$X: revision.o http.o http-push.o $(GITLIBS) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) +git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ + $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT) + $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H) $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h) builtin-revert.o wt-status.o: wt-status.h @@ -1 +1 @@ -Documentation/RelNotes-1.6.4.4.txt
\ No newline at end of file +Documentation/RelNotes-1.6.5.txt
\ No newline at end of file @@ -18,7 +18,7 @@ const char *make_absolute_path(const char *path) { static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1]; char cwd[1024] = ""; - int buf_index = 1, len; + int buf_index = 1; int depth = MAXDEPTH; char *last_elem = NULL; @@ -50,7 +50,7 @@ const char *make_absolute_path(const char *path) die_errno ("Could not get current working directory"); if (last_elem) { - int len = strlen(buf); + size_t len = strlen(buf); if (len + strlen(last_elem) + 2 > PATH_MAX) die ("Too long path name: '%s/%s'", buf, last_elem); @@ -61,7 +61,7 @@ const char *make_absolute_path(const char *path) } if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) { - len = readlink(buf, next_buf, PATH_MAX); + ssize_t len = readlink(buf, next_buf, PATH_MAX); if (len < 0) die_errno ("Invalid symlink '%s'", buf); if (PATH_MAX <= len) diff --git a/advice.c b/advice.c new file mode 100644 index 000000000..ae4b1e81d --- /dev/null +++ b/advice.c @@ -0,0 +1,27 @@ +#include "cache.h" + +int advice_push_nonfastforward = 1; +int advice_status_hints = 1; + +static struct { + const char *name; + int *preference; +} advice_config[] = { + { "pushnonfastforward", &advice_push_nonfastforward }, + { "statushints", &advice_status_hints }, +}; + +int git_default_advice_config(const char *var, const char *value) +{ + const char *k = skip_prefix(var, "advice."); + int i; + + for (i = 0; i < ARRAY_SIZE(advice_config); i++) { + if (strcmp(k, advice_config[i].name)) + continue; + *advice_config[i].preference = git_config_bool(var, value); + return 0; + } + + return 0; +} diff --git a/advice.h b/advice.h new file mode 100644 index 000000000..e9df8e026 --- /dev/null +++ b/advice.h @@ -0,0 +1,9 @@ +#ifndef ADVICE_H +#define ADVICE_H + +extern int advice_push_nonfastforward; +extern int advice_status_hints; + +int git_default_advice_config(const char *var, const char *value); + +#endif /* ADVICE_H */ @@ -283,7 +283,7 @@ static int parse_archive_args(int argc, const char **argv, OPT_STRING(0, "format", &format, "fmt", "archive format"), OPT_STRING(0, "prefix", &base, "prefix", "prepend prefix to each pathname in the archive"), - OPT_STRING(0, "output", &output, "file", + OPT_STRING('o', "output", &output, "file", "write the archive to this file"), OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes, "read .gitattributes in working directory"), diff --git a/arm/sha1.c b/arm/sha1.c deleted file mode 100644 index c61ad4aff..000000000 --- a/arm/sha1.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SHA-1 implementation optimized for ARM - * - * Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org> - * Created: September 17, 2005 - */ - -#include <string.h> -#include "sha1.h" - -extern void arm_sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W); - -void arm_SHA1_Init(arm_SHA_CTX *c) -{ - c->len = 0; - c->hash[0] = 0x67452301; - c->hash[1] = 0xefcdab89; - c->hash[2] = 0x98badcfe; - c->hash[3] = 0x10325476; - c->hash[4] = 0xc3d2e1f0; -} - -void arm_SHA1_Update(arm_SHA_CTX *c, const void *p, unsigned long n) -{ - uint32_t workspace[80]; - unsigned int partial; - unsigned long done; - - partial = c->len & 0x3f; - c->len += n; - if ((partial + n) >= 64) { - if (partial) { - done = 64 - partial; - memcpy(c->buffer + partial, p, done); - arm_sha_transform(c->hash, c->buffer, workspace); - partial = 0; - } else - done = 0; - while (n >= done + 64) { - arm_sha_transform(c->hash, p + done, workspace); - done += 64; - } - } else - done = 0; - if (n - done) - memcpy(c->buffer + partial, p + done, n - done); -} - -void arm_SHA1_Final(unsigned char *hash, arm_SHA_CTX *c) -{ - uint64_t bitlen; - uint32_t bitlen_hi, bitlen_lo; - unsigned int i, offset, padlen; - unsigned char bits[8]; - static const unsigned char padding[64] = { 0x80, }; - - bitlen = c->len << 3; - offset = c->len & 0x3f; - padlen = ((offset < 56) ? 56 : (64 + 56)) - offset; - arm_SHA1_Update(c, padding, padlen); - - bitlen_hi = bitlen >> 32; - bitlen_lo = bitlen & 0xffffffff; - bits[0] = bitlen_hi >> 24; - bits[1] = bitlen_hi >> 16; - bits[2] = bitlen_hi >> 8; - bits[3] = bitlen_hi; - bits[4] = bitlen_lo >> 24; - bits[5] = bitlen_lo >> 16; - bits[6] = bitlen_lo >> 8; - bits[7] = bitlen_lo; - arm_SHA1_Update(c, bits, 8); - - for (i = 0; i < 5; i++) { - uint32_t v = c->hash[i]; - hash[0] = v >> 24; - hash[1] = v >> 16; - hash[2] = v >> 8; - hash[3] = v; - hash += 4; - } -} diff --git a/arm/sha1.h b/arm/sha1.h deleted file mode 100644 index b61b61848..000000000 --- a/arm/sha1.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SHA-1 implementation optimized for ARM - * - * Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org> - * Created: September 17, 2005 - */ - -#include <stdint.h> - -typedef struct { - uint64_t len; - uint32_t hash[5]; - unsigned char buffer[64]; -} arm_SHA_CTX; - -void arm_SHA1_Init(arm_SHA_CTX *c); -void arm_SHA1_Update(arm_SHA_CTX *c, const void *p, unsigned long n); -void arm_SHA1_Final(unsigned char *hash, arm_SHA_CTX *c); - -#define git_SHA_CTX arm_SHA_CTX -#define git_SHA1_Init arm_SHA1_Init -#define git_SHA1_Update arm_SHA1_Update -#define git_SHA1_Final arm_SHA1_Final diff --git a/arm/sha1_arm.S b/arm/sha1_arm.S deleted file mode 100644 index 41e92636e..000000000 --- a/arm/sha1_arm.S +++ /dev/null @@ -1,183 +0,0 @@ -/* - * SHA transform optimized for ARM - * - * Copyright: (C) 2005 by Nicolas Pitre <nico@cam.org> - * Created: September 17, 2005 - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - - .text - .globl arm_sha_transform - -/* - * void sha_transform(uint32_t *hash, const unsigned char *data, uint32_t *W); - * - * note: the "data" pointer may be unaligned. - */ - -arm_sha_transform: - - stmfd sp!, {r4 - r8, lr} - - @ for (i = 0; i < 16; i++) - @ W[i] = ntohl(((uint32_t *)data)[i]); - -#ifdef __ARMEB__ - mov r4, r0 - mov r0, r2 - mov r2, #64 - bl memcpy - mov r2, r0 - mov r0, r4 -#else - mov r3, r2 - mov lr, #16 -1: ldrb r4, [r1], #1 - ldrb r5, [r1], #1 - ldrb r6, [r1], #1 - ldrb r7, [r1], #1 - subs lr, lr, #1 - orr r5, r5, r4, lsl #8 - orr r6, r6, r5, lsl #8 - orr r7, r7, r6, lsl #8 - str r7, [r3], #4 - bne 1b -#endif - - @ for (i = 0; i < 64; i++) - @ W[i+16] = ror(W[i+13] ^ W[i+8] ^ W[i+2] ^ W[i], 31); - - sub r3, r2, #4 - mov lr, #64 -2: ldr r4, [r3, #4]! - subs lr, lr, #1 - ldr r5, [r3, #8] - ldr r6, [r3, #32] - ldr r7, [r3, #52] - eor r4, r4, r5 - eor r4, r4, r6 - eor r4, r4, r7 - mov r4, r4, ror #31 - str r4, [r3, #64] - bne 2b - - /* - * The SHA functions are: - * - * f1(B,C,D) = (D ^ (B & (C ^ D))) - * f2(B,C,D) = (B ^ C ^ D) - * f3(B,C,D) = ((B & C) | (D & (B | C))) - * - * Then the sub-blocks are processed as follows: - * - * A' = ror(A, 27) + f(B,C,D) + E + K + *W++ - * B' = A - * C' = ror(B, 2) - * D' = C - * E' = D - * - * We therefore unroll each loop 5 times to avoid register shuffling. - * Also the ror for C (and also D and E which are successivelyderived - * from it) is applied in place to cut on an additional mov insn for - * each round. - */ - - .macro sha_f1, A, B, C, D, E - ldr r3, [r2], #4 - eor ip, \C, \D - add \E, r1, \E, ror #2 - and ip, \B, ip, ror #2 - add \E, \E, \A, ror #27 - eor ip, ip, \D, ror #2 - add \E, \E, r3 - add \E, \E, ip - .endm - - .macro sha_f2, A, B, C, D, E - ldr r3, [r2], #4 - add \E, r1, \E, ror #2 - eor ip, \B, \C, ror #2 - add \E, \E, \A, ror #27 - eor ip, ip, \D, ror #2 - add \E, \E, r3 - add \E, \E, ip - .endm - - .macro sha_f3, A, B, C, D, E - ldr r3, [r2], #4 - add \E, r1, \E, ror #2 - orr ip, \B, \C, ror #2 - add \E, \E, \A, ror #27 - and ip, ip, \D, ror #2 - add \E, \E, r3 - and r3, \B, \C, ror #2 - orr ip, ip, r3 - add \E, \E, ip - .endm - - ldmia r0, {r4 - r8} - - mov lr, #4 - ldr r1, .L_sha_K + 0 - - /* adjust initial values */ - mov r6, r6, ror #30 - mov r7, r7, ror #30 - mov r8, r8, ror #30 - -3: subs lr, lr, #1 - sha_f1 r4, r5, r6, r7, r8 - sha_f1 r8, r4, r5, r6, r7 - sha_f1 r7, r8, r4, r5, r6 - sha_f1 r6, r7, r8, r4, r5 - sha_f1 r5, r6, r7, r8, r4 - bne 3b - - ldr r1, .L_sha_K + 4 - mov lr, #4 - -4: subs lr, lr, #1 - sha_f2 r4, r5, r6, r7, r8 - sha_f2 r8, r4, r5, r6, r7 - sha_f2 r7, r8, r4, r5, r6 - sha_f2 r6, r7, r8, r4, r5 - sha_f2 r5, r6, r7, r8, r4 - bne 4b - - ldr r1, .L_sha_K + 8 - mov lr, #4 - -5: subs lr, lr, #1 - sha_f3 r4, r5, r6, r7, r8 - sha_f3 r8, r4, r5, r6, r7 - sha_f3 r7, r8, r4, r5, r6 - sha_f3 r6, r7, r8, r4, r5 - sha_f3 r5, r6, r7, r8, r4 - bne 5b - - ldr r1, .L_sha_K + 12 - mov lr, #4 - -6: subs lr, lr, #1 - sha_f2 r4, r5, r6, r7, r8 - sha_f2 r8, r4, r5, r6, r7 - sha_f2 r7, r8, r4, r5, r6 - sha_f2 r6, r7, r8, r4, r5 - sha_f2 r5, r6, r7, r8, r4 - bne 6b - - ldmia r0, {r1, r2, r3, ip, lr} - add r4, r1, r4 - add r5, r2, r5 - add r6, r3, r6, ror #2 - add r7, ip, r7, ror #2 - add r8, lr, r8, ror #2 - stmia r0, {r4 - r8} - - ldmfd sp!, {r4 - r8, pc} - -.L_sha_K: - .word 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c new file mode 100644 index 000000000..d8934757a --- /dev/null +++ b/block-sha1/sha1.c @@ -0,0 +1,282 @@ +/* + * SHA1 routine optimized to do word accesses rather than byte accesses, + * and to avoid unnecessary copies into the context array. + * + * This was initially based on the Mozilla SHA1 implementation, although + * none of the original Mozilla code remains. + */ + +/* this is only to get definitions for memcpy(), ntohl() and htonl() */ +#include "../git-compat-util.h" + +#include "sha1.h" + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +/* + * Force usage of rol or ror by selecting the one with the smaller constant. + * It _can_ generate slightly smaller code (a constant of 1 is special), but + * perhaps more importantly it's possibly faster on any uarch that does a + * rotate with a loop. + */ + +#define SHA_ASM(op, x, n) ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }) +#define SHA_ROL(x,n) SHA_ASM("rol", x, n) +#define SHA_ROR(x,n) SHA_ASM("ror", x, n) + +#else + +#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r))) +#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n)) +#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n) + +#endif + +/* + * If you have 32 registers or more, the compiler can (and should) + * try to change the array[] accesses into registers. However, on + * machines with less than ~25 registers, that won't really work, + * and at least gcc will make an unholy mess of it. + * + * So to avoid that mess which just slows things down, we force + * the stores to memory to actually happen (we might be better off + * with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as + * suggested by Artur Skawina - that will also make gcc unable to + * try to do the silly "optimize away loads" part because it won't + * see what the value will be). + * + * Ben Herrenschmidt reports that on PPC, the C version comes close + * to the optimized asm with this (ie on PPC you don't want that + * 'volatile', since there are lots of registers). + * + * On ARM we get the best code generation by forcing a full memory barrier + * between each SHA_ROUND, otherwise gcc happily get wild with spilling and + * the stack frame size simply explode and performance goes down the drain. + */ + +#if defined(__i386__) || defined(__x86_64__) + #define setW(x, val) (*(volatile unsigned int *)&W(x) = (val)) +#elif defined(__GNUC__) && defined(__arm__) + #define setW(x, val) do { W(x) = (val); __asm__("":::"memory"); } while (0) +#else + #define setW(x, val) (W(x) = (val)) +#endif + +/* + * Performance might be improved if the CPU architecture is OK with + * unaligned 32-bit loads and a fast ntohl() is available. + * Otherwise fall back to byte loads and shifts which is portable, + * and is faster on architectures with memory alignment issues. + */ + +#if defined(__i386__) || defined(__x86_64__) || \ + defined(__ppc__) || defined(__ppc64__) || \ + defined(__powerpc__) || defined(__powerpc64__) || \ + defined(__s390__) || defined(__s390x__) + +#define get_be32(p) ntohl(*(unsigned int *)(p)) +#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0) + +#else + +#define get_be32(p) ( \ + (*((unsigned char *)(p) + 0) << 24) | \ + (*((unsigned char *)(p) + 1) << 16) | \ + (*((unsigned char *)(p) + 2) << 8) | \ + (*((unsigned char *)(p) + 3) << 0) ) +#define put_be32(p, v) do { \ + unsigned int __v = (v); \ + *((unsigned char *)(p) + 0) = __v >> 24; \ + *((unsigned char *)(p) + 1) = __v >> 16; \ + *((unsigned char *)(p) + 2) = __v >> 8; \ + *((unsigned char *)(p) + 3) = __v >> 0; } while (0) + +#endif + +/* This "rolls" over the 512-bit array */ +#define W(x) (array[(x)&15]) + +/* + * Where do we get the source from? The first 16 iterations get it from + * the input data, the next mix it from the 512-bit array. + */ +#define SHA_SRC(t) get_be32(data + t) +#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1) + +#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ + unsigned int TEMP = input(t); setW(t, TEMP); \ + E += TEMP + SHA_ROL(A,5) + (fn) + (constant); \ + B = SHA_ROR(B, 2); } while (0) + +#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E ) +#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E ) +#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E ) +#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E ) + +static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data) +{ + unsigned int A,B,C,D,E; + unsigned int array[16]; + + A = ctx->H[0]; + B = ctx->H[1]; + C = ctx->H[2]; + D = ctx->H[3]; + E = ctx->H[4]; + + /* Round 1 - iterations 0-16 take their input from 'data' */ + T_0_15( 0, A, B, C, D, E); + T_0_15( 1, E, A, B, C, D); + T_0_15( 2, D, E, A, B, C); + T_0_15( 3, C, D, E, A, B); + T_0_15( 4, B, C, D, E, A); + T_0_15( 5, A, B, C, D, E); + T_0_15( 6, E, A, B, C, D); + T_0_15( 7, D, E, A, B, C); + T_0_15( 8, C, D, E, A, B); + T_0_15( 9, B, C, D, E, A); + T_0_15(10, A, B, C, D, E); + T_0_15(11, E, A, B, C, D); + T_0_15(12, D, E, A, B, C); + T_0_15(13, C, D, E, A, B); + T_0_15(14, B, C, D, E, A); + T_0_15(15, A, B, C, D, E); + + /* Round 1 - tail. Input from 512-bit mixing array */ + T_16_19(16, E, A, B, C, D); + T_16_19(17, D, E, A, B, C); + T_16_19(18, C, D, E, A, B); + T_16_19(19, B, C, D, E, A); + + /* Round 2 */ + T_20_39(20, A, B, C, D, E); + T_20_39(21, E, A, B, C, D); + T_20_39(22, D, E, A, B, C); + T_20_39(23, C, D, E, A, B); + T_20_39(24, B, C, D, E, A); + T_20_39(25, A, B, C, D, E); + T_20_39(26, E, A, B, C, D); + T_20_39(27, D, E, A, B, C); + T_20_39(28, C, D, E, A, B); + T_20_39(29, B, C, D, E, A); + T_20_39(30, A, B, C, D, E); + T_20_39(31, E, A, B, C, D); + T_20_39(32, D, E, A, B, C); + T_20_39(33, C, D, E, A, B); + T_20_39(34, B, C, D, E, A); + T_20_39(35, A, B, C, D, E); + T_20_39(36, E, A, B, C, D); + T_20_39(37, D, E, A, B, C); + T_20_39(38, C, D, E, A, B); + T_20_39(39, B, C, D, E, A); + + /* Round 3 */ + T_40_59(40, A, B, C, D, E); + T_40_59(41, E, A, B, C, D); + T_40_59(42, D, E, A, B, C); + T_40_59(43, C, D, E, A, B); + T_40_59(44, B, C, D, E, A); + T_40_59(45, A, B, C, D, E); + T_40_59(46, E, A, B, C, D); + T_40_59(47, D, E, A, B, C); + T_40_59(48, C, D, E, A, B); + T_40_59(49, B, C, D, E, A); + T_40_59(50, A, B, C, D, E); + T_40_59(51, E, A, B, C, D); + T_40_59(52, D, E, A, B, C); + T_40_59(53, C, D, E, A, B); + T_40_59(54, B, C, D, E, A); + T_40_59(55, A, B, C, D, E); + T_40_59(56, E, A, B, C, D); + T_40_59(57, D, E, A, B, C); + T_40_59(58, C, D, E, A, B); + T_40_59(59, B, C, D, E, A); + + /* Round 4 */ + T_60_79(60, A, B, C, D, E); + T_60_79(61, E, A, B, C, D); + T_60_79(62, D, E, A, B, C); + T_60_79(63, C, D, E, A, B); + T_60_79(64, B, C, D, E, A); + T_60_79(65, A, B, C, D, E); + T_60_79(66, E, A, B, C, D); + T_60_79(67, D, E, A, B, C); + T_60_79(68, C, D, E, A, B); + T_60_79(69, B, C, D, E, A); + T_60_79(70, A, B, C, D, E); + T_60_79(71, E, A, B, C, D); + T_60_79(72, D, E, A, B, C); + T_60_79(73, C, D, E, A, B); + T_60_79(74, B, C, D, E, A); + T_60_79(75, A, B, C, D, E); + T_60_79(76, E, A, B, C, D); + T_60_79(77, D, E, A, B, C); + T_60_79(78, C, D, E, A, B); + T_60_79(79, B, C, D, E, A); + + ctx->H[0] += A; + ctx->H[1] += B; + ctx->H[2] += C; + ctx->H[3] += D; + ctx->H[4] += E; +} + +void blk_SHA1_Init(blk_SHA_CTX *ctx) +{ + ctx->size = 0; + + /* Initialize H with the magic constants (see FIPS180 for constants) */ + ctx->H[0] = 0x67452301; + ctx->H[1] = 0xefcdab89; + ctx->H[2] = 0x98badcfe; + ctx->H[3] = 0x10325476; + ctx->H[4] = 0xc3d2e1f0; +} + +void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) +{ + int lenW = ctx->size & 63; + + ctx->size += len; + + /* Read the data into W and process blocks as they get full */ + if (lenW) { + int left = 64 - lenW; + if (len < left) + left = len; + memcpy(lenW + (char *)ctx->W, data, left); + lenW = (lenW + left) & 63; + len -= left; + data = ((const char *)data + left); + if (lenW) + return; + blk_SHA1_Block(ctx, ctx->W); + } + while (len >= 64) { + blk_SHA1_Block(ctx, data); + data = ((const char *)data + 64); + len -= 64; + } + if (len) + memcpy(ctx->W, data, len); +} + +void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) +{ + static const unsigned char pad[64] = { 0x80 }; + unsigned int padlen[2]; + int i; + + /* Pad with a binary 1 (ie 0x80), then zeroes, then length */ + padlen[0] = htonl(ctx->size >> 29); + padlen[1] = htonl(ctx->size << 3); + + i = ctx->size & 63; + blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i))); + blk_SHA1_Update(ctx, padlen, 8); + + /* Output hash */ + for (i = 0; i < 5; i++) + put_be32(hashout + i*4, ctx->H[i]); +} diff --git a/block-sha1/sha1.h b/block-sha1/sha1.h new file mode 100644 index 000000000..b864df623 --- /dev/null +++ b/block-sha1/sha1.h @@ -0,0 +1,22 @@ +/* + * SHA1 routine optimized to do word accesses rather than byte accesses, + * and to avoid unnecessary copies into the context array. + * + * This was initially based on the Mozilla SHA1 implementation, although + * none of the original Mozilla code remains. + */ + +typedef struct { + unsigned long long size; + unsigned int H[5]; + unsigned int W[16]; +} blk_SHA_CTX; + +void blk_SHA1_Init(blk_SHA_CTX *ctx); +void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); +void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); + +#define git_SHA_CTX blk_SHA_CTX +#define git_SHA1_Init blk_SHA1_Init +#define git_SHA1_Update blk_SHA1_Update +#define git_SHA1_Final blk_SHA1_Final diff --git a/builtin-add.c b/builtin-add.c index 581a2a174..cb6e5906f 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -105,8 +105,8 @@ static void refresh(int verbose, const char **pathspec) for (specs = 0; pathspec[specs]; specs++) /* nothing */; seen = xcalloc(specs, 1); - refresh_index(&the_index, verbose ? REFRESH_SAY_CHANGED : REFRESH_QUIET, - pathspec, seen); + refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, + pathspec, seen, "Unstaged changes after refreshing the index:"); for (i = 0; i < specs; i++) { if (!seen[i]) die("pathspec '%s' did not match any files", pathspec[i]); @@ -131,27 +131,27 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p return pathspec; } -int interactive_add(int argc, const char **argv, const char *prefix) +int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec) { - int status, ac; + int status, ac, pc = 0; const char **args; - const char **pathspec = NULL; - if (argc) { - pathspec = validate_pathspec(argc, argv, prefix); - if (!pathspec) - return -1; - } + if (pathspec) + while (pathspec[pc]) + pc++; - args = xcalloc(sizeof(const char *), (argc + 4)); + args = xcalloc(sizeof(const char *), (pc + 5)); ac = 0; args[ac++] = "add--interactive"; - if (patch_interactive) - args[ac++] = "--patch"; + if (patch_mode) + args[ac++] = patch_mode; + if (revision) + args[ac++] = revision; args[ac++] = "--"; - if (argc) { - memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc); - ac += argc; + if (pc) { + memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc); + ac += pc; } args[ac] = NULL; @@ -160,6 +160,21 @@ int interactive_add(int argc, const char **argv, const char *prefix) return status; } +int interactive_add(int argc, const char **argv, const char *prefix) +{ + const char **pathspec = NULL; + + if (argc) { + pathspec = validate_pathspec(argc, argv, prefix); + if (!pathspec) + return -1; + } + + return run_add_interactive(NULL, + patch_interactive ? "--patch" : NULL, + pathspec); +} + static int edit_patch(int argc, const char **argv, const char *prefix) { char *file = xstrdup(git_path("ADD_EDIT.patch")); @@ -183,7 +198,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) out = open(file, O_CREAT | O_WRONLY, 0644); if (out < 0) die ("Could not open '%s' for writing.", file); - rev.diffopt.file = fdopen(out, "w"); + rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) die ("Could not write patch"); diff --git a/builtin-apply.c b/builtin-apply.c index 39dc96ae0..c8372a0a8 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -61,6 +61,13 @@ static enum ws_error_action { static int whitespace_error; static int squelch_whitespace_errors = 5; static int applied_after_fixing_ws; + +static enum ws_ignore { + ignore_ws_none, + ignore_ws_change, +} ws_ignore_action = ignore_ws_none; + + static const char *patch_input_file; static const char *root; static int root_len; @@ -97,6 +104,21 @@ static void parse_whitespace_option(const char *option) die("unrecognized whitespace option '%s'", option); } +static void parse_ignorewhitespace_option(const char *option) +{ + if (!option || !strcmp(option, "no") || + !strcmp(option, "false") || !strcmp(option, "never") || + !strcmp(option, "none")) { + ws_ignore_action = ignore_ws_none; + return; + } + if (!strcmp(option, "change")) { + ws_ignore_action = ignore_ws_change; + return; + } + die("unrecognized whitespace ignore option '%s'", option); +} + static void set_default_whitespace_mode(const char *whitespace_option) { if (!whitespace_option && !apply_default_whitespace) @@ -214,6 +236,62 @@ static uint32_t hash_line(const char *cp, size_t len) return h; } +/* + * Compare lines s1 of length n1 and s2 of length n2, ignoring + * whitespace difference. Returns 1 if they match, 0 otherwise + */ +static int fuzzy_matchlines(const char *s1, size_t n1, + const char *s2, size_t n2) +{ + const char *last1 = s1 + n1 - 1; + const char *last2 = s2 + n2 - 1; + int result = 0; + + if (n1 < 0 || n2 < 0) + return 0; + + /* ignore line endings */ + while ((*last1 == '\r') || (*last1 == '\n')) + last1--; + while ((*last2 == '\r') || (*last2 == '\n')) + last2--; + + /* skip leading whitespace */ + while (isspace(*s1) && (s1 <= last1)) + s1++; + while (isspace(*s2) && (s2 <= last2)) + s2++; + /* early return if both lines are empty */ + if ((s1 > last1) && (s2 > last2)) + return 1; + while (!result) { + result = *s1++ - *s2++; + /* + * Skip whitespace inside. We check for whitespace on + * both buffers because we don't want "a b" to match + * "ab" + */ + if (isspace(*s1) && isspace(*s2)) { + while (isspace(*s1) && s1 <= last1) + s1++; + while (isspace(*s2) && s2 <= last2) + s2++; + } + /* + * If we reached the end on one side only, + * lines don't match + */ + if ( + ((s2 > last2) && (s1 <= last1)) || + ((s1 > last1) && (s2 <= last2))) + return 0; + if ((s1 > last1) && (s2 > last2)) + break; + } + + return !result; +} + static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag) { ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc); @@ -1672,10 +1750,17 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) } } +/* + * Update the preimage, and the common lines in postimage, + * from buffer buf of length len. If postlen is 0 the postimage + * is updated in place, otherwise it's updated on a new buffer + * of length postlen + */ + static void update_pre_post_images(struct image *preimage, struct image *postimage, char *buf, - size_t len) + size_t len, size_t postlen) { int i, ctx; char *new, *old, *fixed; @@ -1694,11 +1779,19 @@ static void update_pre_post_images(struct image *preimage, *preimage = fixed_preimage; /* - * Adjust the common context lines in postimage, in place. - * This is possible because whitespace fixing does not make - * the string grow. + * Adjust the common context lines in postimage. This can be + * done in-place when we are just doing whitespace fixing, + * which does not make the string grow, but needs a new buffer + * when ignoring whitespace causes the update, since in this case + * we could have e.g. tabs converted to multiple spaces. + * We trust the caller to tell us if the update can be done + * in place (postlen==0) or not. */ - new = old = postimage->buf; + old = postimage->buf; + if (postlen) + new = postimage->buf = xmalloc(postlen); + else + new = old; fixed = preimage->buf; for (i = ctx = 0; i < postimage->nr; i++) { size_t len = postimage->line[i].len; @@ -1773,12 +1866,56 @@ static int match_fragment(struct image *img, !memcmp(img->buf + try, preimage->buf, preimage->len)) return 1; + /* + * No exact match. If we are ignoring whitespace, run a line-by-line + * fuzzy matching. We collect all the line length information because + * we need it to adjust whitespace if we match. + */ + if (ws_ignore_action == ignore_ws_change) { + size_t imgoff = 0; + size_t preoff = 0; + size_t postlen = postimage->len; + for (i = 0; i < preimage->nr; i++) { + size_t prelen = preimage->line[i].len; + size_t imglen = img->line[try_lno+i].len; + + if (!fuzzy_matchlines(img->buf + try + imgoff, imglen, + preimage->buf + preoff, prelen)) + return 0; + if (preimage->line[i].flag & LINE_COMMON) + postlen += imglen - prelen; + imgoff += imglen; + preoff += prelen; + } + + /* + * Ok, the preimage matches with whitespace fuzz. Update it and + * the common postimage lines to use the same whitespace as the + * target. imgoff now holds the true length of the target that + * matches the preimage, and we need to update the line lengths + * of the preimage to match the target ones. + */ + fixed_buf = xmalloc(imgoff); + memcpy(fixed_buf, img->buf + try, imgoff); + for (i = 0; i < preimage->nr; i++) + preimage->line[i].len = img->line[try_lno+i].len; + + /* + * Update the preimage buffer and the postimage context lines. + */ + update_pre_post_images(preimage, postimage, + fixed_buf, imgoff, postlen); + return 1; + } + if (ws_error_action != correct_ws_error) return 0; /* * The hunk does not apply byte-by-byte, but the hash says - * it might with whitespace fuzz. + * it might with whitespace fuzz. We haven't been asked to + * ignore whitespace, we were asked to correct whitespace + * errors, so let's try matching after whitespace correction. */ fixed_buf = xmalloc(preimage->len + 1); buf = fixed_buf; @@ -1830,7 +1967,7 @@ static int match_fragment(struct image *img, * hunk match. Update the context lines in the postimage. */ update_pre_post_images(preimage, postimage, - fixed_buf, buf - fixed_buf); + fixed_buf, buf - fixed_buf, 0); return 1; unmatch_exit: @@ -3272,6 +3409,8 @@ static int git_apply_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "apply.whitespace")) return git_config_string(&apply_default_whitespace, var, value); + else if (!strcmp(var, "apply.ignorewhitespace")) + return git_config_string(&apply_default_ignorewhitespace, var, value); return git_default_config(var, value, cb); } @@ -3308,6 +3447,16 @@ static int option_parse_z(const struct option *opt, return 0; } +static int option_parse_space_change(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + ws_ignore_action = ignore_ws_none; + else + ws_ignore_action = ignore_ws_change; + return 0; +} + static int option_parse_whitespace(const struct option *opt, const char *arg, int unset) { @@ -3384,6 +3533,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action", "detect new or modified lines that have whitespace errors", 0, option_parse_whitespace }, + { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL, + "ignore changes in whitespace when finding context", + PARSE_OPT_NOARG, option_parse_space_change }, + { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL, + "ignore changes in whitespace when finding context", + PARSE_OPT_NOARG, option_parse_space_change }, OPT_BOOLEAN('R', "reverse", &apply_in_reverse, "apply the patch in reverse"), OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero, @@ -3408,6 +3563,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) git_config(git_apply_config, NULL); if (apply_default_whitespace) parse_whitespace_option(apply_default_whitespace); + if (apply_default_ignorewhitespace) + parse_ignorewhitespace_option(apply_default_ignorewhitespace); argc = parse_options(argc, argv, prefix, builtin_apply_options, apply_usage, 0); diff --git a/builtin-archive.c b/builtin-archive.c index f9a4bea41..12351e9dd 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -60,6 +60,17 @@ 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 "zip"; + return NULL; +} + #define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ PARSE_OPT_KEEP_ARGV0 | \ PARSE_OPT_KEEP_UNKNOWN | \ @@ -70,21 +81,39 @@ 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 = NULL; struct option local_opts[] = { - OPT_STRING(0, "output", &output, "file", + OPT_STRING('o', "output", &output, "file", "write the archive to this file"), OPT_STRING(0, "remote", &remote, "repo", "retrieve the archive from remote repository <repo>"), OPT_STRING(0, "exec", &exec, "cmd", "path to the remote git-upload-archive command"), + OPT_STRING(0, "format", &format, "fmt", "archive format"), OPT_END() }; + char fmt_opt[32]; argc = parse_options(argc, argv, prefix, local_opts, NULL, PARSE_OPT_KEEP_ALL); - if (output) + if (output) { create_output_file(output); + if (!format) + format = format_from_name(output); + } + + if (format) { + sprintf(fmt_opt, "--format=%s", format); + /* + * This is safe because either --format and/or --output must + * have been given on the original command line if we get to + * this point, and parse_options() must have eaten at least + * one argument, i.e. we have enough room to append to argv[]. + */ + argv[argc++] = fmt_opt; + argv[argc] = NULL; + } if (remote) return run_remote_archiver(argc, argv, remote, exec); diff --git a/builtin-blame.c b/builtin-blame.c index fd6ca51ee..7512773b4 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1348,7 +1348,7 @@ static void get_ac_line(const char *inbuf, const char *what, /* * Now, convert both name and e-mail using mailmap */ - if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { + if (map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { /* Add a trailing '>' to email, since map_user returns plain emails Note: It already has '<', since we replace from mail+1 */ mailpos = memchr(mail, '\0', mail_len); diff --git a/builtin-branch.c b/builtin-branch.c index 1a03d5f35..9f5799206 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -586,7 +586,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('m', NULL, &rename, "move/rename a branch and its reflog", 1), OPT_BIT('M', NULL, &rename, "move/rename a branch, even if target exists", 2), OPT_BOOLEAN('l', NULL, &reflog, "create the branch's reflog"), - OPT_BOOLEAN('f', NULL, &force_create, "force creation (when already exists)"), + OPT_BOOLEAN('f', "force", &force_create, "force creation (when already exists)"), { OPTION_CALLBACK, 0, "no-merged", &merge_filter_ref, "commit", "print only not merged branches", diff --git a/builtin-checkout.c b/builtin-checkout.c index c6d6ac99f..d050c3789 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -566,6 +566,13 @@ static int git_checkout_config(const char *var, const char *value, void *cb) return git_xmerge_config(var, value, cb); } +static int interactive_checkout(const char *revision, const char **pathspec, + struct checkout_opts *opts) +{ + return run_add_interactive(revision, "--patch=checkout", pathspec); +} + + int cmd_checkout(int argc, const char **argv, const char *prefix) { struct checkout_opts opts; @@ -574,6 +581,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) struct branch_info new; struct tree *source_tree = NULL; char *conflict_style = NULL; + int patch_mode = 0; struct option options[] = { OPT__QUIET(&opts.quiet), OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"), @@ -584,10 +592,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) 2), OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage", 3), - OPT_BOOLEAN('f', NULL, &opts.force, "force"), + OPT_BOOLEAN('f', "force", &opts.force, "force"), OPT_BOOLEAN('m', "merge", &opts.merge, "merge"), OPT_STRING(0, "conflict", &conflict_style, "style", "conflict style (merge or diff3)"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END(), }; int has_dash_dash; @@ -602,6 +611,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); + if (patch_mode && (opts.track > 0 || opts.new_branch + || opts.new_branch_log || opts.merge || opts.force)) + die ("--patch is incompatible with all other options"); + /* --track without -b should DWIM */ if (0 < opts.track && !opts.new_branch) { const char *argv0 = argv[0]; @@ -708,6 +721,9 @@ no_reference: if (!pathspec) die("invalid path specification"); + if (patch_mode) + return interactive_checkout(new.name, pathspec, &opts); + /* Checkout paths */ if (opts.new_branch) { if (argc == 1) { @@ -723,6 +739,9 @@ no_reference: return checkout_paths(source_tree, pathspec, &opts); } + if (patch_mode) + return interactive_checkout(new.name, NULL, &opts); + if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; if (strbuf_check_branch_ref(&buf, opts.new_branch)) diff --git a/builtin-clean.c b/builtin-clean.c index 05c763cbe..28cdcd027 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -41,7 +41,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct option options[] = { OPT__QUIET(&quiet), OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', NULL, &force, "force"), + OPT_BOOLEAN('f', "force", &force, "force"), OPT_BOOLEAN('d', NULL, &remove_directories, "remove whole directories"), OPT_BOOLEAN('x', NULL, &ignored, "remove ignored files, too"), diff --git a/builtin-clone.c b/builtin-clone.c index e51978a15..bab2d84ea 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -38,9 +38,10 @@ static const char * const builtin_clone_usage[] = { }; static int option_quiet, option_no_checkout, option_bare, option_mirror; -static int option_local, option_no_hardlinks, option_shared; +static int option_local, option_no_hardlinks, option_shared, option_recursive; static char *option_template, *option_reference, *option_depth; static char *option_origin = NULL; +static char *option_branch = NULL; static char *option_upload_pack = "git-upload-pack"; static int option_verbose; @@ -59,12 +60,16 @@ static struct option builtin_clone_options[] = { "don't use local hardlinks, always copy"), OPT_BOOLEAN('s', "shared", &option_shared, "setup as shared repository"), + OPT_BOOLEAN(0, "recursive", &option_recursive, + "setup as shared repository"), OPT_STRING(0, "template", &option_template, "path", "path the template repository"), OPT_STRING(0, "reference", &option_reference, "repo", "reference repository"), OPT_STRING('o', "origin", &option_origin, "branch", "use <branch> instead of 'origin' to track upstream"), + OPT_STRING('b', "branch", &option_branch, "branch", + "checkout <branch> instead of the remote's HEAD"), OPT_STRING('u', "upload-pack", &option_upload_pack, "path", "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", @@ -73,6 +78,10 @@ static struct option builtin_clone_options[] = { OPT_END() }; +static const char *argv_submodule[] = { + "submodule", "update", "--init", "--recursive", NULL +}; + static char *get_repo_path(const char *repo, int *is_bundle) { static char *suffix[] = { "/.git", ".git", "" }; @@ -260,7 +269,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) die_errno("failed to create link '%s'", dest->buf); option_no_hardlinks = 1; } - if (copy_file(dest->buf, src->buf, 0666)) + if (copy_file_with_time(dest->buf, src->buf, 0666)) die_errno("failed to copy file to '%s'", dest->buf); } closedir(dir); @@ -347,7 +356,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; int dest_exists; - const struct ref *refs, *head_points_at, *remote_head, *mapped_refs; + const struct ref *refs, *remote_head, *mapped_refs; + const struct ref *remote_head_points_at; + const struct ref *our_head_points_at; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; @@ -509,7 +520,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_upload_pack); refs = transport_get_remote_refs(transport); - if(refs) + if (refs) transport_fetch_refs(transport, refs); } @@ -519,11 +530,31 @@ int cmd_clone(int argc, const char **argv, const char *prefix) mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf); remote_head = find_ref_by_name(refs, "HEAD"); - head_points_at = guess_remote_head(remote_head, mapped_refs, 0); + remote_head_points_at = + guess_remote_head(remote_head, mapped_refs, 0); + + if (option_branch) { + struct strbuf head = STRBUF_INIT; + strbuf_addstr(&head, src_ref_prefix); + strbuf_addstr(&head, option_branch); + our_head_points_at = + find_ref_by_name(mapped_refs, head.buf); + strbuf_release(&head); + + if (!our_head_points_at) { + warning("Remote branch %s not found in " + "upstream %s, using HEAD instead", + option_branch, option_origin); + our_head_points_at = remote_head_points_at; + } + } + else + our_head_points_at = remote_head_points_at; } else { warning("You appear to have cloned an empty repository."); - head_points_at = NULL; + our_head_points_at = NULL; + remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; if (!option_bare) @@ -531,41 +562,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix) "refs/heads/master"); } - if (head_points_at) { - /* Local default branch link */ - create_symref("HEAD", head_points_at->name, NULL); + if (remote_head_points_at && !option_bare) { + struct strbuf head_ref = STRBUF_INIT; + strbuf_addstr(&head_ref, branch_top.buf); + strbuf_addstr(&head_ref, "HEAD"); + create_symref(head_ref.buf, + remote_head_points_at->peer_ref->name, + reflog_msg.buf); + } + if (our_head_points_at) { + /* Local default branch link */ + create_symref("HEAD", our_head_points_at->name, NULL); if (!option_bare) { - struct strbuf head_ref = STRBUF_INIT; - const char *head = head_points_at->name; - - if (!prefixcmp(head, "refs/heads/")) - head += 11; - - /* Set up the initial local branch */ - - /* Local branch initial value */ + const char *head = skip_prefix(our_head_points_at->name, + "refs/heads/"); update_ref(reflog_msg.buf, "HEAD", - head_points_at->old_sha1, + our_head_points_at->old_sha1, NULL, 0, DIE_ON_ERR); - - strbuf_addstr(&head_ref, branch_top.buf); - strbuf_addstr(&head_ref, "HEAD"); - - /* Remote branch link */ - create_symref(head_ref.buf, - head_points_at->peer_ref->name, - reflog_msg.buf); - install_branch_config(0, head, option_origin, - head_points_at->name); + our_head_points_at->name); } } else if (remote_head) { /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) + if (!option_bare) { update_ref(reflog_msg.buf, "HEAD", remote_head->old_sha1, NULL, REF_NODEREF, DIE_ON_ERR); + our_head_points_at = remote_head; + } } else { /* Nothing to checkout out */ if (!option_no_checkout) @@ -599,7 +624,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) opts.src_index = &the_index; opts.dst_index = &the_index; - tree = parse_tree_indirect(remote_head->old_sha1); + tree = parse_tree_indirect(our_head_points_at->old_sha1); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); unpack_trees(1, &t, &opts); @@ -610,6 +635,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), sha1_to_hex(remote_head->old_sha1), "1", NULL); + + if (!err && option_recursive) + err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); } strbuf_release(&reflog_msg); diff --git a/builtin-commit.c b/builtin-commit.c index 4bcce06fb..200ffdaad 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -51,7 +51,7 @@ static const char *template_file; static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, no_verify, allow_empty; +static int quiet, verbose, no_verify, allow_empty, dry_run; static char *untracked_files_arg; /* * The default commit message cleanup mode will remove the lines @@ -103,6 +103,7 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN(0, "interactive", &interactive, "interactively add files"), OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), + OPT_BOOLEAN(0, "dry-run", &dry_run, "show what would be committed"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), @@ -217,12 +218,15 @@ static void create_base_index(void) exit(128); /* We've already reported the error, finish dying */ } -static char *prepare_index(int argc, const char **argv, const char *prefix) +static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status) { int fd; struct string_list partial; const char **pathspec = NULL; + int refresh_flags = REFRESH_QUIET; + if (is_status) + refresh_flags |= REFRESH_UNMERGED; if (interactive) { if (interactive_add(argc, argv, prefix) != 0) die("interactive add failed"); @@ -253,7 +257,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) if (all || (also && pathspec && *pathspec)) { int fd = hold_locked_index(&index_lock, 1); add_files_to_cache(also ? prefix : NULL, pathspec, 0); - refresh_cache(REFRESH_QUIET); + refresh_cache(refresh_flags); if (write_cache(fd, active_cache, active_nr) || close_lock_file(&index_lock)) die("unable to write new_index file"); @@ -272,7 +276,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) */ if (!pathspec || !*pathspec) { fd = hold_locked_index(&index_lock, 1); - refresh_cache(REFRESH_QUIET); + refresh_cache(refresh_flags); if (write_cache(fd, active_cache, active_nr) || commit_locked_index(&index_lock)) die("unable to write new_index file"); @@ -339,27 +343,24 @@ static char *prepare_index(int argc, const char **argv, const char *prefix) return false_lock.filename; } -static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn) +static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn, + struct wt_status *s) { - struct wt_status s; - - wt_status_prepare(&s); - if (wt_status_relative_paths) - s.prefix = prefix; + if (s->relative_paths) + s->prefix = prefix; if (amend) { - s.amend = 1; - s.reference = "HEAD^1"; + s->amend = 1; + s->reference = "HEAD^1"; } - s.verbose = verbose; - s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES); - s.index_file = index_file; - s.fp = fp; - s.nowarn = nowarn; + s->verbose = verbose; + s->index_file = index_file; + s->fp = fp; + s->nowarn = nowarn; - wt_status_print(&s); + wt_status_print(s); - return s.commitable; + return s->commitable; } static int is_a_merge(const unsigned char *sha1) @@ -413,7 +414,8 @@ static void determine_author_info(void) author_date = date; } -static int prepare_to_commit(const char *index_file, const char *prefix) +static int prepare_to_commit(const char *index_file, const char *prefix, + struct wt_status *s) { struct stat statbuf; int commitable, saved_color_setting; @@ -555,10 +557,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (ident_shown) fprintf(fp, "#\n"); - saved_color_setting = wt_status_use_color; - wt_status_use_color = 0; - commitable = run_status(fp, index_file, prefix, 1); - wt_status_use_color = saved_color_setting; + saved_color_setting = s->use_color; + s->use_color = 0; + commitable = run_status(fp, index_file, prefix, 1, s); + s->use_color = saved_color_setting; } else { unsigned char sha1[20]; const char *parent = "HEAD"; @@ -579,7 +581,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (!commitable && !in_merge && !allow_empty && !(amend && is_a_merge(head_sha1))) { - run_status(stdout, index_file, prefix, 0); + run_status(stdout, index_file, prefix, 0, s); return 0; } @@ -691,7 +693,8 @@ static const char *find_author_by_nickname(const char *name) static int parse_and_validate_options(int argc, const char *argv[], const char * const usage[], - const char *prefix) + const char *prefix, + struct wt_status *s) { int f = 0; @@ -794,11 +797,11 @@ static int parse_and_validate_options(int argc, const char *argv[], if (!untracked_files_arg) ; /* default already initialized */ else if (!strcmp(untracked_files_arg, "no")) - show_untracked_files = SHOW_NO_UNTRACKED_FILES; + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; else if (!strcmp(untracked_files_arg, "normal")) - show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; else if (!strcmp(untracked_files_arg, "all")) - show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; else die("Invalid untracked files mode '%s'", untracked_files_arg); @@ -810,28 +813,93 @@ static int parse_and_validate_options(int argc, const char *argv[], return argc; } -int cmd_status(int argc, const char **argv, const char *prefix) +static int dry_run_commit(int argc, const char **argv, const char *prefix, + struct wt_status *s) { - const char *index_file; int commitable; + const char *index_file; - git_config(git_status_config, NULL); + index_file = prepare_index(argc, argv, prefix, 1); + commitable = run_status(stdout, index_file, prefix, 0, s); + rollback_index_files(); - if (wt_status_use_color == -1) - wt_status_use_color = git_use_color_default; + return commitable ? 0 : 1; +} - if (diff_use_color_default == -1) - diff_use_color_default = git_use_color_default; +static int parse_status_slot(const char *var, int offset) +{ + if (!strcasecmp(var+offset, "header")) + return WT_STATUS_HEADER; + if (!strcasecmp(var+offset, "updated") + || !strcasecmp(var+offset, "added")) + return WT_STATUS_UPDATED; + if (!strcasecmp(var+offset, "changed")) + return WT_STATUS_CHANGED; + if (!strcasecmp(var+offset, "untracked")) + return WT_STATUS_UNTRACKED; + if (!strcasecmp(var+offset, "nobranch")) + return WT_STATUS_NOBRANCH; + if (!strcasecmp(var+offset, "unmerged")) + return WT_STATUS_UNMERGED; + die("bad config variable '%s'", var); +} - argc = parse_and_validate_options(argc, argv, builtin_status_usage, prefix); +static int git_status_config(const char *k, const char *v, void *cb) +{ + struct wt_status *s = cb; - index_file = prepare_index(argc, argv, prefix); + if (!strcmp(k, "status.submodulesummary")) { + int is_bool; + s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); + if (is_bool && s->submodule_summary) + s->submodule_summary = -1; + return 0; + } + if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { + s->use_color = git_config_colorbool(k, v, -1); + return 0; + } + if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { + int slot = parse_status_slot(k, 13); + if (!v) + return config_error_nonbool(k); + color_parse(v, k, s->color_palette[slot]); + return 0; + } + if (!strcmp(k, "status.relativepaths")) { + s->relative_paths = git_config_bool(k, v); + return 0; + } + if (!strcmp(k, "status.showuntrackedfiles")) { + if (!v) + return config_error_nonbool(k); + else if (!strcmp(v, "no")) + s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(v, "normal")) + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(v, "all")) + s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + return error("Invalid untracked files mode '%s'", v); + return 0; + } + return git_diff_ui_config(k, v, NULL); +} - commitable = run_status(stdout, index_file, prefix, 0); +int cmd_status(int argc, const char **argv, const char *prefix) +{ + struct wt_status s; - rollback_index_files(); + wt_status_prepare(&s); + git_config(git_status_config, &s); + if (s.use_color == -1) + s.use_color = git_use_color_default; + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; - return commitable ? 0 : 1; + argc = parse_and_validate_options(argc, argv, builtin_status_usage, + prefix, &s); + return dry_run_commit(argc, argv, prefix, &s); } static void print_summary(const char *prefix, const unsigned char *sha1) @@ -883,10 +951,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1) static int git_commit_config(const char *k, const char *v, void *cb) { + struct wt_status *s = cb; + if (!strcmp(k, "commit.template")) return git_config_string(&template_file, k, v); - return git_status_config(k, v, cb); + return git_status_config(k, v, s); } int cmd_commit(int argc, const char **argv, const char *prefix) @@ -899,19 +969,26 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct commit_list *parents = NULL, **pptr = &parents; struct stat statbuf; int allow_fast_forward = 1; + struct wt_status s; - git_config(git_commit_config, NULL); - - if (wt_status_use_color == -1) - wt_status_use_color = git_use_color_default; + wt_status_prepare(&s); + git_config(git_commit_config, &s); - argc = parse_and_validate_options(argc, argv, builtin_commit_usage, prefix); + if (s.use_color == -1) + s.use_color = git_use_color_default; - index_file = prepare_index(argc, argv, prefix); + argc = parse_and_validate_options(argc, argv, builtin_commit_usage, + prefix, &s); + if (dry_run) { + if (diff_use_color_default == -1) + diff_use_color_default = git_use_color_default; + return dry_run_commit(argc, argv, prefix, &s); + } + index_file = prepare_index(argc, argv, prefix, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ - if (!prepare_to_commit(index_file, prefix)) { + if (!prepare_to_commit(index_file, prefix, &s)) { rollback_index_files(); return 1; } diff --git a/builtin-describe.c b/builtin-describe.c index 7a662980d..df67a733a 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -20,6 +20,7 @@ static int tags; /* Allow lightweight tags */ static int longformat; static int abbrev = DEFAULT_ABBREV; static int max_candidates = 10; +static int found_names; static const char *pattern; static int always; @@ -49,6 +50,7 @@ static void add_to_known_names(const char *path, memcpy(e->path, path, len); commit->util = e; } + found_names = 1; } static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data) @@ -195,6 +197,9 @@ static void describe(const char *arg, int last_one) for_each_ref(get_name, NULL); } + if (!found_names) + die("cannot describe '%s'", sha1_to_hex(sha1)); + n = cmit->util; if (n) { /* diff --git a/builtin-diff.c b/builtin-diff.c index 2e51f408f..ffcdd055c 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -218,6 +218,8 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv revs->max_count = 3; else if (!strcmp(argv[1], "-q")) options |= DIFF_SILENT_ON_REMOVED; + else if (!strcmp(argv[1], "-h")) + usage(builtin_diff_usage); else return error("invalid option: %s", argv[1]); argv++; argc--; diff --git a/builtin-fast-export.c b/builtin-fast-export.c index c48c18d0c..b0a4029c9 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 no_data; static int parse_opt_signed_tag_mode(const struct option *opt, const char *arg, int unset) @@ -116,6 +117,9 @@ static void handle_object(const unsigned char *sha1) char *buf; struct object *object; + if (no_data) + return; + if (is_null_sha1(sha1)) return; @@ -173,7 +177,7 @@ static void show_filemodify(struct diff_queue_struct *q, * Links refer to objects in another repositories; * output the SHA-1 verbatim. */ - if (S_ISGITLINK(spec->mode)) + if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s %s\n", spec->mode, sha1_to_hex(spec->sha1), spec->path); else { @@ -580,6 +584,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) "Import marks from this file"), OPT_BOOLEAN(0, "fake-missing-tagger", &fake_missing_tagger, "Fake a tagger when tags lack one"), + { OPTION_NEGBIT, 0, "data", &no_data, NULL, + "Skip output of blob data", + PARSE_OPT_NOARG | PARSE_OPT_NEGHELP, NULL, 1 }, OPT_END() }; diff --git a/builtin-fetch.c b/builtin-fetch.c index 817dd6bff..cb48c57ca 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -454,7 +454,7 @@ static int quickfetch(struct ref *ref_map) for (ref = ref_map; ref; ref = ref->next) { if (write_in_full(revlist.in, sha1_to_hex(ref->old_sha1), 40) < 0 || - write_in_full(revlist.in, "\n", 1) < 0) { + write_str_in_full(revlist.in, "\n") < 0) { if (errno != EPIPE && errno != EINVAL) error("failed write to rev-list: %s", strerror(errno)); err = -1; diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index d7cc8cafb..a5a83f146 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -576,7 +576,7 @@ static void populate_value(struct refinfo *ref) if (!prefixcmp(name, "refname")) refname = ref->refname; - else if(!prefixcmp(name, "upstream")) { + else if (!prefixcmp(name, "upstream")) { struct branch *branch; /* only local branches may have an upstream */ if (prefixcmp(ref->refname, "refs/heads/")) diff --git a/builtin-fsck.c b/builtin-fsck.c index b3d38fa27..c58b0e337 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -589,6 +589,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) struct alternate_object_database *alt; errors_found = 0; + read_replace_refs = 0; argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); if (write_lost_and_found) { diff --git a/builtin-grep.c b/builtin-grep.c index fd450bc16..761799d7d 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -54,25 +54,57 @@ static int grep_config(const char *var, const char *value, void *cb) } /* + * Return non-zero if max_depth is negative or path has no more then max_depth + * slashes. + */ +static int accept_subdir(const char *path, int max_depth) +{ + if (max_depth < 0) + return 1; + + while ((path = strchr(path, '/')) != NULL) { + max_depth--; + if (max_depth < 0) + return 0; + path++; + } + return 1; +} + +/* + * Return non-zero if name is a subdirectory of match and is not too deep. + */ +static int is_subdir(const char *name, int namelen, + const char *match, int matchlen, int max_depth) +{ + if (matchlen > namelen || strncmp(name, match, matchlen)) + return 0; + + if (name[matchlen] == '\0') /* exact match */ + return 1; + + if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/') + return accept_subdir(name + matchlen + 1, max_depth); + + return 0; +} + +/* * git grep pathspecs are somewhat different from diff-tree pathspecs; * pathname wildcards are allowed. */ -static int pathspec_matches(const char **paths, const char *name) +static int pathspec_matches(const char **paths, const char *name, int max_depth) { int namelen, i; if (!paths || !*paths) - return 1; + return accept_subdir(name, max_depth); namelen = strlen(name); for (i = 0; paths[i]; i++) { const char *match = paths[i]; int matchlen = strlen(match); const char *cp, *meta; - if (!matchlen || - ((matchlen <= namelen) && - !strncmp(name, match, matchlen) && - (match[matchlen-1] == '/' || - name[matchlen] == '\0' || name[matchlen] == '/'))) + if (is_subdir(name, namelen, match, matchlen, max_depth)) return 1; if (!fnmatch(match, name, 0)) return 1; @@ -411,7 +443,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) int kept; if (!S_ISREG(ce->ce_mode)) continue; - if (!pathspec_matches(paths, ce->name)) + if (!pathspec_matches(paths, ce->name, opt->max_depth)) continue; name = ce->name; if (name[0] == '-') { @@ -469,7 +501,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached, struct cache_entry *ce = active_cache[nr]; if (!S_ISREG(ce->ce_mode)) continue; - if (!pathspec_matches(paths, ce->name)) + if (!pathspec_matches(paths, ce->name, opt->max_depth)) continue; /* * If CE_VALID is on, we assume worktree file and its cache entry @@ -529,7 +561,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, strbuf_addch(&pathbuf, '/'); down = pathbuf.buf + tn_len; - if (!pathspec_matches(paths, down)) + if (!pathspec_matches(paths, down, opt->max_depth)) ; else if (S_ISREG(entry.mode)) hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len); @@ -683,6 +715,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_SET_INT('I', NULL, &opt.binary, "don't match patterns in binary files", GREP_BINARY_NOMATCH), + { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth", + "descend at most <depth> levels", PARSE_OPT_NONEG, + NULL, 1 }, OPT_GROUP(""), OPT_BIT('E', "extended-regexp", &opt.regflags, "use extended POSIX regular expressions", REG_EXTENDED), @@ -760,6 +795,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.pathname = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; + opt.max_depth = -1; strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD); opt.color = -1; diff --git a/builtin-init-db.c b/builtin-init-db.c index 4a5600631..dd84caecb 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -6,6 +6,7 @@ #include "cache.h" #include "builtin.h" #include "exec_cmd.h" +#include "parse-options.h" #ifndef DEFAULT_GIT_TEMPLATE_DIR #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" @@ -370,8 +371,16 @@ static int guess_repository_type(const char *git_dir) return 1; } -static const char init_db_usage[] = -"git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]]"; +static int shared_callback(const struct option *opt, const char *arg, int unset) +{ + *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP; + return 0; +} + +static const char *const init_db_usage[] = { + "git init [-q | --quiet] [--bare] [--template=<template-directory>] [--shared[=<permissions>]] [directory]", + NULL +}; /* * If you want to, you can share the DB area with any number of branches. @@ -384,25 +393,60 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *git_dir; const char *template_dir = NULL; unsigned int flags = 0; - int i; - - for (i = 1; i < argc; i++, argv++) { - const char *arg = argv[1]; - if (!prefixcmp(arg, "--template=")) - template_dir = arg+11; - else if (!strcmp(arg, "--bare")) { - static char git_dir[PATH_MAX+1]; - is_bare_repository_cfg = 1; - setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, - sizeof(git_dir)), 0); - } else if (!strcmp(arg, "--shared")) - init_shared_repository = PERM_GROUP; - else if (!prefixcmp(arg, "--shared=")) - init_shared_repository = git_config_perm("arg", arg+9); - else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet")) - flags |= INIT_DB_QUIET; - else - usage(init_db_usage); + const struct option init_db_options[] = { + OPT_STRING(0, "template", &template_dir, "template-directory", + "provide the directory from which templates will be used"), + OPT_SET_INT(0, "bare", &is_bare_repository_cfg, + "create a bare repository", 1), + { OPTION_CALLBACK, 0, "shared", &init_shared_repository, + "permissions", + "specify that the git repository is to be shared amongst several users", + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, shared_callback, 0}, + OPT_BIT('q', "quiet", &flags, "be quiet", INIT_DB_QUIET), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); + + if (argc == 1) { + int mkdir_tried = 0; + retry: + if (chdir(argv[0]) < 0) { + if (!mkdir_tried) { + int saved; + /* + * At this point we haven't read any configuration, + * and we know shared_repository should always be 0; + * but just in case we play safe. + */ + saved = shared_repository; + shared_repository = 0; + switch (safe_create_leading_directories_const(argv[0])) { + case -3: + errno = EEXIST; + /* fallthru */ + case -1: + die_errno("cannot mkdir %s", argv[0]); + break; + default: + break; + } + shared_repository = saved; + if (mkdir(argv[0], 0777) < 0) + die_errno("cannot mkdir %s", argv[0]); + mkdir_tried = 1; + goto retry; + } + die_errno("cannot chdir to %s", argv[0]); + } + } else if (0 < argc) { + usage(init_db_usage[0]); + } + if (is_bare_repository_cfg == 1) { + static char git_dir[PATH_MAX+1]; + + setenv(GIT_DIR_ENVIRONMENT, + getcwd(git_dir, sizeof(git_dir)), 0); } if (init_shared_repository != -1) diff --git a/builtin-log.c b/builtin-log.c index 8d93c1a2a..25e21ed41 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -27,6 +27,10 @@ static int default_show_root = 1; static const char *fmt_patch_subject_prefix = "PATCH"; static const char *fmt_pretty; +static const char * const builtin_log_usage = + "git log [<options>] [<since>..<until>] [[--] <path>...]\n" + " or: git show [options] <object>..."; + static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev) { @@ -69,6 +73,8 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, die("invalid --decorate option: %s", arg); } else if (!strcmp(arg, "--source")) { rev->show_source = 1; + } else if (!strcmp(arg, "-h")) { + usage(builtin_log_usage); } else die("unrecognized argument: %s", arg); } diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 92637ac0b..d498b1cd2 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -25,6 +25,7 @@ static enum { static struct strbuf charset = STRBUF_INIT; static int patch_lines; static struct strbuf **p_hdr_data, **s_hdr_data; +static int use_scissors; #define MAX_HDR_PARSED 10 #define MAX_BOUNDARIES 5 @@ -712,6 +713,56 @@ static inline int patchbreak(const struct strbuf *line) return 0; } +static int is_scissors_line(const struct strbuf *line) +{ + size_t i, len = line->len; + int scissors = 0, gap = 0; + int first_nonblank = -1; + int last_nonblank = 0, visible, perforation = 0, in_perforation = 0; + const char *buf = line->buf; + + for (i = 0; i < len; i++) { + if (isspace(buf[i])) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = i; + if (first_nonblank < 0) + first_nonblank = i; + if (buf[i] == '-') { + in_perforation = 1; + perforation++; + continue; + } + if (i + 1 < len && + (!memcmp(buf + i, ">8", 2) || !memcmp(buf + i, "8<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + i++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + visible = last_nonblank - first_nonblank + 1; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + static int handle_commit_msg(struct strbuf *line) { static int still_looking = 1; @@ -723,7 +774,8 @@ static int handle_commit_msg(struct strbuf *line) strbuf_ltrim(line); if (!line->len) return 0; - if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) + still_looking = check_header(line, s_hdr_data, 0); + if (still_looking) return 0; } @@ -731,6 +783,24 @@ static int handle_commit_msg(struct strbuf *line) if (metainfo_charset) convert_to_utf8(line, charset.buf); + if (use_scissors && is_scissors_line(line)) { + int i; + rewind(cmitmsg); + ftruncate(fileno(cmitmsg), 0); + still_looking = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (s_hdr_data[i]) + strbuf_release(s_hdr_data[i]); + s_hdr_data[i] = NULL; + } + return 0; + } + if (patchbreak(line)) { fclose(cmitmsg); cmitmsg = NULL; @@ -765,7 +835,6 @@ static void handle_filter(struct strbuf *line) static void handle_body(void) { - int len = 0; struct strbuf prev = STRBUF_INIT; /* Skip up to the first boundary */ @@ -775,8 +844,6 @@ static void handle_body(void) } do { - strbuf_setlen(&line, line.len + len); - /* process any boundary lines */ if (*content_top && is_multipart_boundary(&line)) { /* flush any leftover */ @@ -832,10 +899,7 @@ static void handle_body(void) handle_filter(&line); } - strbuf_reset(&line); - if (strbuf_avail(&line) < 100) - strbuf_grow(&line, 100); - } while ((len = read_line_with_nul(line.buf, strbuf_avail(&line), fin))); + } while (!strbuf_getwholeline(&line, fin, '\n')); handle_body_out: strbuf_release(&prev); @@ -891,12 +955,9 @@ static void handle_info(void) fprintf(fout, "\n"); } -static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, - const char *msg, const char *patch) +static int mailinfo(FILE *in, FILE *out, const char *msg, const char *patch) { int peek; - keep_subject = ks; - metainfo_charset = encoding; fin = in; fout = out; @@ -930,8 +991,20 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, return 0; } +static int git_mailinfo_config(const char *var, const char *value, void *unused) +{ + if (prefixcmp(var, "mailinfo.")) + return git_default_config(var, value, unused); + if (!strcmp(var, "mailinfo.scissors")) { + use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + static const char mailinfo_usage[] = - "git mailinfo [-k] [-u | --encoding=<encoding> | -n] msg patch <mail >info"; + "git mailinfo [-k] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] msg patch < mail >info"; int cmd_mailinfo(int argc, const char **argv, const char *prefix) { @@ -940,7 +1013,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) /* NEEDSWORK: might want to do the optional .git/ directory * discovery */ - git_config(git_default_config, NULL); + git_config(git_mailinfo_config, NULL); def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); metainfo_charset = def_charset; @@ -954,6 +1027,10 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) metainfo_charset = NULL; else if (!prefixcmp(argv[1], "--encoding=")) metainfo_charset = argv[1] + 11; + else if (!strcmp(argv[1], "--scissors")) + use_scissors = 1; + else if (!strcmp(argv[1], "--no-scissors")) + use_scissors = 0; else usage(mailinfo_usage); argc--; argv++; @@ -962,5 +1039,5 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) if (argc != 3) usage(mailinfo_usage); - return !!mailinfo(stdin, stdout, keep_subject, metainfo_charset, argv[1], argv[2]); + return !!mailinfo(stdin, stdout, argv[1], argv[2]); } diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index ad5f6b593..dfe5b151e 100644 --- a/builtin-mailsplit.c +++ b/builtin-mailsplit.c @@ -7,6 +7,7 @@ #include "cache.h" #include "builtin.h" #include "string-list.h" +#include "strbuf.h" static const char git_mailsplit_usage[] = "git mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> [<mbox>|<Maildir>...]"; @@ -42,26 +43,8 @@ static int is_from_line(const char *line, int len) return 1; } -/* Could be as small as 64, enough to hold a Unix "From " line. */ -static char buf[4096]; - -/* We cannot use fgets() because our lines can contain NULs */ -int read_line_with_nul(char *buf, int size, FILE *in) -{ - int len = 0, c; - - for (;;) { - c = getc(in); - if (c == EOF) - break; - buf[len++] = c; - if (c == '\n' || len + 1 >= size) - break; - } - buf[len] = '\0'; - - return len; -} +static struct strbuf buf = STRBUF_INIT; +static int keep_cr; /* Called with the first line (potentially partial) * already in buf[] -- normally that should begin with @@ -71,10 +54,9 @@ int read_line_with_nul(char *buf, int size, FILE *in) static int split_one(FILE *mbox, const char *name, int allow_bare) { FILE *output = NULL; - int len = strlen(buf); int fd; int status = 0; - int is_bare = !is_from_line(buf, len); + int is_bare = !is_from_line(buf.buf, buf.len); if (is_bare && !allow_bare) goto corrupt; @@ -82,26 +64,29 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); if (fd < 0) die_errno("cannot open output file '%s'", name); - output = fdopen(fd, "w"); + output = xfdopen(fd, "w"); /* Copy it out, while searching for a line that begins with * "From " and having something that looks like a date format. */ for (;;) { - int is_partial = len && buf[len-1] != '\n'; + if (!keep_cr && buf.len > 1 && buf.buf[buf.len-1] == '\n' && + buf.buf[buf.len-2] == '\r') { + strbuf_setlen(&buf, buf.len-2); + strbuf_addch(&buf, '\n'); + } - if (fwrite(buf, 1, len, output) != len) + if (fwrite(buf.buf, 1, buf.len, output) != buf.len) die_errno("cannot write output"); - len = read_line_with_nul(buf, sizeof(buf), mbox); - if (len == 0) { + if (strbuf_getwholeline(&buf, mbox, '\n')) { if (feof(mbox)) { status = 1; break; } die_errno("cannot read mbox"); } - if (!is_partial && !is_bare && is_from_line(buf, len)) + if (!is_bare && is_from_line(buf.buf, buf.len)) break; /* done with one message */ } fclose(output); @@ -166,7 +151,7 @@ static int split_maildir(const char *maildir, const char *dir, goto out; } - if (fgets(buf, sizeof(buf), f) == NULL) { + if (strbuf_getwholeline(&buf, f, '\n')) { error("cannot read mail %s (%s)", file, strerror(errno)); goto out; } @@ -203,7 +188,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, } while (isspace(peek)); ungetc(peek, f); - if (fgets(buf, sizeof(buf), f) == NULL) { + if (strbuf_getwholeline(&buf, f, '\n')) { /* empty stdin is OK */ if (f != stdin) { error("cannot read mbox %s", file); @@ -248,6 +233,8 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix) nr = strtol(arg+2, NULL, 10); } else if ( arg[1] == 'b' && !arg[2] ) { allow_bare = 1; + } else if (!strcmp(arg, "--keep-cr")) { + keep_cr = 1; } else if ( arg[1] == 'o' && arg[2] ) { dir = arg+2; } else if ( arg[1] == '-' && !arg[2] ) { diff --git a/builtin-merge-base.c b/builtin-merge-base.c index a6ec2f7ab..54e7ec223 100644 --- a/builtin-merge-base.c +++ b/builtin-merge-base.c @@ -23,7 +23,7 @@ static int show_merge_base(struct commit **rev, int rev_nr, int show_all) } static const char * const merge_base_usage[] = { - "git merge-base [--all] <commit-id> <commit-id>...", + "git merge-base [-a|--all] <commit> <commit>...", NULL }; diff --git a/builtin-merge.c b/builtin-merge.c index f4de73fa9..b6b84286b 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -598,7 +598,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, discard_cache(); if (read_cache() < 0) die("failed to read the cache"); - return -ret; + return ret; } } diff --git a/builtin-mv.c b/builtin-mv.c index b592c367b..1b20028c6 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -53,7 +53,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; struct option builtin_mv_options[] = { OPT__DRY_RUN(&show_only), - OPT_BOOLEAN('f', NULL, &force, "force move/rename even if target exists"), + OPT_BOOLEAN('f', "force", &force, "force move/rename even if target exists"), OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"), OPT_END(), }; diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 9cc8a8451..7a390e1d4 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1008,6 +1008,33 @@ static void add_preferred_base(unsigned char *sha1) it->pcache.tree_size = size; } +static void cleanup_preferred_base(void) +{ + struct pbase_tree *it; + unsigned i; + + it = pbase_tree; + pbase_tree = NULL; + while (it) { + struct pbase_tree *this = it; + it = this->next; + free(this->pcache.tree_data); + free(this); + } + + for (i = 0; i < ARRAY_SIZE(pbase_tree_cache); i++) { + if (!pbase_tree_cache[i]) + continue; + free(pbase_tree_cache[i]->tree_data); + free(pbase_tree_cache[i]); + pbase_tree_cache[i] = NULL; + } + + free(done_pbase_paths); + done_pbase_paths = NULL; + done_pbase_paths_num = done_pbase_paths_alloc = 0; +} + static void check_object(struct object_entry *entry) { if (entry->in_pack) { @@ -1599,7 +1626,7 @@ static void *threaded_find_deltas(void *arg) static void ll_find_deltas(struct object_entry **list, unsigned list_size, int window, int depth, unsigned *processed) { - struct thread_params p[delta_search_threads]; + struct thread_params *p; int i, ret, active_threads = 0; if (delta_search_threads <= 1) { @@ -1609,6 +1636,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, if (progress > pack_to_stdout) fprintf(stderr, "Delta compression using up to %d threads.\n", delta_search_threads); + p = xcalloc(delta_search_threads, sizeof(*p)); /* Partition the work amongst work threads. */ for (i = 0; i < delta_search_threads; i++) { @@ -1717,6 +1745,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, active_threads--; } } + free(p); } #else @@ -1808,7 +1837,7 @@ static void prepare_pack(int window, int depth) static int git_pack_config(const char *k, const char *v, void *cb) { - if(!strcmp(k, "pack.window")) { + if (!strcmp(k, "pack.window")) { window = git_config_int(k, v); return 0; } @@ -2098,6 +2127,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) int rp_ac_alloc = 64; int rp_ac; + read_replace_refs = 0; + rp_av = xcalloc(rp_ac_alloc, sizeof(*rp_av)); rp_av[0] = "pack-objects"; @@ -2308,6 +2339,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) rp_av[rp_ac] = NULL; get_object_list(rp_ac, rp_av); } + cleanup_preferred_base(); if (include_tag && nr_result) for_each_ref(add_ref_tag, NULL); stop_progress(&progress_state); diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index 00590b1c3..be99eb0ac 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "progress.h" +#include "parse-options.h" -static const char prune_packed_usage[] = -"git prune-packed [-n] [-q]"; +static const char * const prune_packed_usage[] = { + "git prune-packed [-n|--dry-run] [-q|--quiet]", + NULL +}; #define DRY_RUN 01 #define VERBOSE 02 @@ -68,24 +71,16 @@ void prune_packed_objects(int opts) int cmd_prune_packed(int argc, const char **argv, const char *prefix) { - int i; int opts = VERBOSE; + const struct option prune_packed_options[] = { + OPT_BIT('n', "dry-run", &opts, "dry run", DRY_RUN), + OPT_NEGBIT('q', "quiet", &opts, "be quiet", VERBOSE), + OPT_END() + }; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, prefix, prune_packed_options, + prune_packed_usage, 0); - if (*arg == '-') { - if (!strcmp(arg, "-n")) - opts |= DRY_RUN; - else if (!strcmp(arg, "-q")) - opts &= ~VERBOSE; - else - usage(prune_packed_usage); - continue; - } - /* Handle arguments here .. */ - usage(prune_packed_usage); - } prune_packed_objects(opts); return 0; } diff --git a/builtin-prune.c b/builtin-prune.c index 0ed9cce4a..8459aec8e 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -140,6 +140,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) char *s; save_commit_buffer = 0; + read_replace_refs = 0; init_revisions(&revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); diff --git a/builtin-push.c b/builtin-push.c index f8376cffa..3cb1ee46d 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,7 +10,7 @@ #include "parse-options.h" static const char * const push_usage[] = { - "git push [--all | --mirror] [--dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", + "git push [--all | --mirror] [-n | --dry-run] [--porcelain] [--tags] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [-v] [<repository> <refspec>...]", NULL, }; @@ -157,7 +157,7 @@ static int do_push(const char *repo, int flags) continue; error("failed to push some refs to '%s'", url[i]); - if (nonfastforward) { + if (nonfastforward && advice_push_nonfastforward) { printf("To prevent you from losing history, non-fast-forward updates were rejected\n" "Merge the remote changes before pushing again. See the 'non-fast forward'\n" "section of 'git push --help' for details.\n"); @@ -175,13 +175,14 @@ int cmd_push(int argc, const char **argv, const char *prefix) const char *repo = NULL; /* default repository */ struct option options[] = { + OPT_BIT('q', "quiet", &flags, "be quiet", TRANSPORT_PUSH_QUIET), OPT_BIT('v', "verbose", &flags, "be verbose", TRANSPORT_PUSH_VERBOSE), OPT_STRING( 0 , "repo", &repo, "repository", "repository"), OPT_BIT( 0 , "all", &flags, "push all refs", TRANSPORT_PUSH_ALL), OPT_BIT( 0 , "mirror", &flags, "mirror all refs", (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE)), OPT_BOOLEAN( 0 , "tags", &tags, "push tags"), - OPT_BIT( 0 , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), + OPT_BIT('n' , "dry-run", &flags, "dry run", TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, "machine-readable output", TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, "force updates", TRANSPORT_PUSH_FORCE), OPT_BOOLEAN( 0 , "thin", &thin, "use thin pack"), diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 82e25eaa0..14c836b16 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -12,6 +12,7 @@ #include "unpack-trees.h" #include "dir.h" #include "builtin.h" +#include "parse-options.h" static int nr_trees; static struct tree *trees[MAX_UNPACK_TREES]; @@ -29,7 +30,39 @@ static int list_tree(unsigned char *sha1) return 0; } -static const char read_tree_usage[] = "git read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])"; +static const char * const read_tree_usage[] = { + "git read-tree [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u [--exclude-per-directory=<gitignore>] | -i]] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]]", + NULL +}; + +static int index_output_cb(const struct option *opt, const char *arg, + int unset) +{ + set_alternate_index_output(arg); + return 0; +} + +static int exclude_per_directory_cb(const struct option *opt, const char *arg, + int unset) +{ + struct dir_struct *dir; + struct unpack_trees_options *opts; + + opts = (struct unpack_trees_options *)opt->value; + + if (opts->dir) + die("more than one --exclude-per-directory given."); + + dir = xcalloc(1, sizeof(*opts->dir)); + dir->flags |= DIR_SHOW_IGNORED; + dir->exclude_per_dir = arg; + opts->dir = dir; + /* We do not need to nor want to do read-directory + * here; we are merely interested in reusing the + * per directory ignore stack mechanism. + */ + return 0; +} static struct lock_file lock_file; @@ -39,6 +72,34 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) unsigned char sha1[20]; struct tree_desc t[MAX_UNPACK_TREES]; struct unpack_trees_options opts; + int prefix_set = 0; + const struct option read_tree_options[] = { + { OPTION_CALLBACK, 0, "index-output", NULL, "FILE", + "write resulting index to <FILE>", + PARSE_OPT_NONEG, index_output_cb }, + OPT__VERBOSE(&opts.verbose_update), + OPT_GROUP("Merging"), + OPT_SET_INT('m', NULL, &opts.merge, + "perform a merge in addition to a read", 1), + OPT_SET_INT(0, "trivial", &opts.trivial_merges_only, + "3-way merge if no file level merging required", 1), + OPT_SET_INT(0, "aggressive", &opts.aggressive, + "3-way merge in presence of adds and removes", 1), + OPT_SET_INT(0, "reset", &opts.reset, + "same as -m, but discard unmerged entries", 1), + { OPTION_STRING, 0, "prefix", &opts.prefix, "<subdirectory>/", + "read the tree into the index under <subdirectory>/", + PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP }, + OPT_SET_INT('u', NULL, &opts.update, + "update working tree with merge result", 1), + { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, + "gitignore", + "allow explicitly ignored files to be overwritten", + PARSE_OPT_NONEG, exclude_per_directory_cb }, + OPT_SET_INT('i', NULL, &opts.index_only, + "don't check the working tree after merging", 1), + OPT_END() + }; memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; @@ -49,105 +110,21 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) newfd = hold_locked_index(&lock_file, 1); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - /* "-u" means "update", meaning that a merge will update - * the working tree. - */ - if (!strcmp(arg, "-u")) { - opts.update = 1; - continue; - } + argc = parse_options(argc, argv, unused_prefix, read_tree_options, + read_tree_usage, 0); - if (!strcmp(arg, "-v")) { - opts.verbose_update = 1; - continue; - } + prefix_set = opts.prefix ? 1 : 0; + if (1 < opts.merge + opts.reset + prefix_set) + die("Which one? -m, --reset, or --prefix?"); - /* "-i" means "index only", meaning that a merge will - * not even look at the working tree. - */ - if (!strcmp(arg, "-i")) { - opts.index_only = 1; - continue; - } - - if (!prefixcmp(arg, "--index-output=")) { - set_alternate_index_output(arg + 15); - continue; - } - - /* "--prefix=<subdirectory>/" means keep the current index - * entries and put the entries from the tree under the - * given subdirectory. - */ - if (!prefixcmp(arg, "--prefix=")) { - if (stage || opts.merge || opts.prefix) - usage(read_tree_usage); - opts.prefix = arg + 9; - opts.merge = 1; - stage = 1; - if (read_cache_unmerged()) - die("you need to resolve your current index first"); - continue; - } - - /* This differs from "-m" in that we'll silently ignore - * unmerged entries and overwrite working tree files that - * correspond to them. - */ - if (!strcmp(arg, "--reset")) { - if (stage || opts.merge || opts.prefix) - usage(read_tree_usage); - opts.reset = 1; - opts.merge = 1; - stage = 1; - read_cache_unmerged(); - continue; - } - - if (!strcmp(arg, "--trivial")) { - opts.trivial_merges_only = 1; - continue; - } - - if (!strcmp(arg, "--aggressive")) { - opts.aggressive = 1; - continue; - } - - /* "-m" stands for "merge", meaning we start in stage 1 */ - if (!strcmp(arg, "-m")) { - if (stage || opts.merge || opts.prefix) - usage(read_tree_usage); - if (read_cache_unmerged()) - die("you need to resolve your current index first"); - stage = 1; - opts.merge = 1; - continue; - } - - if (!prefixcmp(arg, "--exclude-per-directory=")) { - struct dir_struct *dir; - - if (opts.dir) - die("more than one --exclude-per-directory are given."); - - dir = xcalloc(1, sizeof(*opts.dir)); - dir->flags |= DIR_SHOW_IGNORED; - dir->exclude_per_dir = arg + 24; - opts.dir = dir; - /* We do not need to nor want to do read-directory - * here; we are merely interested in reusing the - * per directory ignore stack mechanism. - */ - continue; - } + if (opts.reset || opts.merge || opts.prefix) { + if (read_cache_unmerged() && (opts.prefix || opts.merge)) + die("You need to resolve your current index first"); + stage = opts.merge = 1; + } - /* using -u and -i at the same time makes no sense */ - if (1 < opts.index_only + opts.update) - usage(read_tree_usage); + for (i = 0; i < argc; i++) { + const char *arg = argv[i]; if (get_sha1(arg, sha1)) die("Not a valid object name %s", arg); @@ -155,8 +132,11 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } + if (1 < opts.index_only + opts.update) + die("-u and -i at the same time makes no sense"); if ((opts.update||opts.index_only) && !opts.merge) - usage(read_tree_usage); + die("%s is meaningless without -m, --reset, or --prefix", + opts.update ? "-u" : "-i"); if ((opts.dir && !opts.update)) die("--exclude-per-directory is meaningless unless -u"); if (opts.merge && !opts.index_only) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 6ec1d056e..b771fe9b2 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -123,31 +123,6 @@ static struct command *commands; static const char pre_receive_hook[] = "hooks/pre-receive"; static const char post_receive_hook[] = "hooks/post-receive"; -static int run_status(int code, const char *cmd_name) -{ - switch (code) { - case 0: - return 0; - case -ERR_RUN_COMMAND_FORK: - return error("fork of %s failed", cmd_name); - case -ERR_RUN_COMMAND_EXEC: - return error("execute of %s failed", cmd_name); - case -ERR_RUN_COMMAND_PIPE: - return error("pipe failed"); - case -ERR_RUN_COMMAND_WAITPID: - return error("waitpid failed"); - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - return error("waitpid is confused"); - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - return error("%s died of signal", cmd_name); - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - return error("%s died strangely", cmd_name); - default: - error("%s exited with error code %d", cmd_name, -code); - return -code; - } -} - static int run_receive_hook(const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; @@ -174,7 +149,7 @@ static int run_receive_hook(const char *hook_name) code = start_command(&proc); if (code) - return run_status(code, hook_name); + return code; for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) { size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n", @@ -186,7 +161,7 @@ static int run_receive_hook(const char *hook_name) } } close(proc.in); - return run_status(finish_command(&proc), hook_name); + return finish_command(&proc); } static int run_update_hook(struct command *cmd) @@ -203,9 +178,8 @@ static int run_update_hook(struct command *cmd) argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - return run_status(run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | - RUN_COMMAND_STDOUT_TO_STDERR), - update_hook); + return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | + RUN_COMMAND_STDOUT_TO_STDERR); } static int is_ref_checked_out(const char *ref) @@ -419,7 +393,6 @@ static void run_update_post_hook(struct command *cmd) argv[argc] = NULL; status = run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_COMMAND_STDOUT_TO_STDERR); - run_status(status, update_post_hook); } static void execute_commands(const char *unpacker_error) @@ -537,7 +510,6 @@ static const char *unpack(void) code = run_command_v_opt(unpacker, RUN_GIT_CMD); if (!code) return NULL; - run_status(code, unpacker[0]); return "unpack-objects abnormal exit"; } else { const char *keeper[7]; @@ -563,7 +535,6 @@ static const char *unpack(void) ip.git_cmd = 1; status = start_command(&ip); if (status) { - run_status(status, keeper[0]); return "index-pack fork failed"; } pack_lockfile = index_pack_lockfile(ip.out); @@ -573,7 +544,6 @@ static const char *unpack(void) reprepare_packed_git(); return NULL; } - run_status(status, keeper[0]); return "index-pack abnormal exit"; } } diff --git a/builtin-reflog.c b/builtin-reflog.c index 95198c5de..e23b5ef97 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -362,7 +362,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, } else if (cmd->updateref && (write_in_full(lock->lock_fd, sha1_to_hex(cb.last_kept_sha1), 40) != 40 || - write_in_full(lock->lock_fd, "\n", 1) != 1 || + write_str_in_full(lock->lock_fd, "\n") != 1 || close_ref(lock) < 0)) { status |= error("Couldn't write %s", lock->lk->filename); diff --git a/builtin-remote.c b/builtin-remote.c index 008abfe09..0777dd719 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -385,7 +385,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), fetch_map, 1); - for(ref = matches; ref; ref = ref->next) + for (ref = matches; ref; ref = ref->next) string_list_append(abbrev_branch(ref->name), &states->heads); free_refs(fetch_map); @@ -484,7 +484,7 @@ static int read_remote_branches(const char *refname, const char *symref; strbuf_addf(&buf, "refs/remotes/%s", rename->old); - if(!prefixcmp(refname, buf.buf)) { + if (!prefixcmp(refname, buf.buf)) { item = string_list_append(xstrdup(refname), rename->remote_branches); symref = resolve_ref(refname, orig_sha1, 1, &flag); if (flag & REF_ISSYMREF) diff --git a/builtin-replace.c b/builtin-replace.c new file mode 100644 index 000000000..fe3a647a3 --- /dev/null +++ b/builtin-replace.c @@ -0,0 +1,159 @@ +/* + * Builtin "git replace" + * + * Copyright (c) 2008 Christian Couder <chriscool@tuxfamily.org> + * + * Based on builtin-tag.c by Kristian Høgsberg <krh@redhat.com> + * and Carlos Rica <jasampler@gmail.com> that was itself based on + * git-tag.sh and mktag.c by Linus Torvalds. + */ + +#include "cache.h" +#include "builtin.h" +#include "refs.h" +#include "parse-options.h" + +static const char * const git_replace_usage[] = { + "git replace [-f] <object> <replacement>", + "git replace -d <object>...", + "git replace -l [<pattern>]", + NULL +}; + +static int show_reference(const char *refname, const unsigned char *sha1, + int flag, void *cb_data) +{ + const char *pattern = cb_data; + + if (!fnmatch(pattern, refname, 0)) + printf("%s\n", refname); + + return 0; +} + +static int list_replace_refs(const char *pattern) +{ + if (pattern == NULL) + pattern = "*"; + + for_each_replace_ref(show_reference, (void *) pattern); + + return 0; +} + +typedef int (*each_replace_name_fn)(const char *name, const char *ref, + const unsigned char *sha1); + +static int for_each_replace_name(const char **argv, each_replace_name_fn fn) +{ + const char **p; + char ref[PATH_MAX]; + int had_error = 0; + unsigned char sha1[20]; + + for (p = argv; *p; p++) { + if (snprintf(ref, sizeof(ref), "refs/replace/%s", *p) + >= sizeof(ref)) { + error("replace ref name too long: %.*s...", 50, *p); + had_error = 1; + continue; + } + if (!resolve_ref(ref, sha1, 1, NULL)) { + error("replace ref '%s' not found.", *p); + had_error = 1; + continue; + } + if (fn(*p, ref, sha1)) + had_error = 1; + } + return had_error; +} + +static int delete_replace_ref(const char *name, const char *ref, + const unsigned char *sha1) +{ + if (delete_ref(ref, sha1, 0)) + return 1; + printf("Deleted replace ref '%s'\n", name); + return 0; +} + +static int replace_object(const char *object_ref, const char *replace_ref, + int force) +{ + unsigned char object[20], prev[20], repl[20]; + char ref[PATH_MAX]; + struct ref_lock *lock; + + if (get_sha1(object_ref, object)) + die("Failed to resolve '%s' as a valid ref.", object_ref); + if (get_sha1(replace_ref, repl)) + die("Failed to resolve '%s' as a valid ref.", replace_ref); + + if (snprintf(ref, sizeof(ref), + "refs/replace/%s", + sha1_to_hex(object)) > sizeof(ref) - 1) + die("replace ref name too long: %.*s...", 50, ref); + if (check_ref_format(ref)) + die("'%s' is not a valid ref name.", ref); + + if (!resolve_ref(ref, prev, 1, NULL)) + hashclr(prev); + else if (!force) + die("replace ref '%s' already exists", ref); + + lock = lock_any_ref_for_update(ref, prev, 0); + if (!lock) + die("%s: cannot lock the ref", ref); + if (write_ref_sha1(lock, repl, NULL) < 0) + die("%s: cannot update the ref", ref); + + return 0; +} + +int cmd_replace(int argc, const char **argv, const char *prefix) +{ + int list = 0, delete = 0, force = 0; + struct option options[] = { + OPT_BOOLEAN('l', NULL, &list, "list replace refs"), + OPT_BOOLEAN('d', NULL, &delete, "delete replace refs"), + OPT_BOOLEAN('f', NULL, &force, "replace the ref if it exists"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0); + + if (list && delete) + usage_msg_opt("-l and -d cannot be used together", + git_replace_usage, options); + + if (force && (list || delete)) + usage_msg_opt("-f cannot be used with -d or -l", + git_replace_usage, options); + + /* Delete refs */ + if (delete) { + if (argc < 1) + usage_msg_opt("-d needs at least one argument", + git_replace_usage, options); + return for_each_replace_name(argv, delete_replace_ref); + } + + /* Replace object */ + if (!list && argc) { + if (argc != 2) + usage_msg_opt("bad number of arguments", + git_replace_usage, options); + return replace_object(argv[0], argv[1], force); + } + + /* List refs, even if "list" is not set */ + if (argc > 1) + usage_msg_opt("only one pattern can be given with -l", + git_replace_usage, options); + if (force) + usage_msg_opt("-f needs some arguments", + git_replace_usage, options); + + return list_replace_refs(argv[0]); +} diff --git a/builtin-reset.c b/builtin-reset.c index 5fa1789d0..73e60223d 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -108,7 +108,8 @@ static int update_index_refresh(int fd, struct lock_file *index_lock, int flags) if (read_cache() < 0) return error("Could not read index"); - result = refresh_cache(flags) ? 1 : 0; + result = refresh_index(&the_index, (flags), NULL, NULL, + "Unstaged changes after reset:") ? 1 : 0; if (write_cache(fd, active_cache, active_nr) || commit_locked_index(index_lock)) return error ("Could not refresh index"); @@ -142,6 +143,17 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } +static int interactive_reset(const char *revision, const char **argv, + const char *prefix) +{ + const char **pathspec = NULL; + + if (*argv) + pathspec = get_pathspec(prefix, argv); + + return run_add_interactive(revision, "--patch=reset", pathspec); +} + static int read_from_tree(const char *prefix, const char **argv, unsigned char *tree_sha1, int refresh_flags) { @@ -183,6 +195,7 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) int cmd_reset(int argc, const char **argv, const char *prefix) { int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; + int patch_mode = 0; const char *rev = "HEAD"; unsigned char sha1[20], *orig = NULL, sha1_orig[20], *old_orig = NULL, sha1_old_orig[20]; @@ -198,6 +211,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) "reset HEAD, index and working tree", MERGE), OPT_BOOLEAN('q', NULL, &quiet, "disable showing new HEAD in hard reset and progress message"), + OPT_BOOLEAN('p', "patch", &patch_mode, "select hunks interactively"), OPT_END() }; @@ -251,6 +265,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die("Could not parse object '%s'.", rev); hashcpy(sha1, commit->object.sha1); + if (patch_mode) { + if (reset_type != NONE) + die("--patch is incompatible with --{hard,mixed,soft}"); + return interactive_reset(rev, argv + i, prefix); + } + /* git reset tree [--] paths... can be used to * load chosen paths from the tree into the index without * affecting the working tree nor HEAD. */ @@ -261,7 +281,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) die("Cannot do %s reset with paths.", reset_type_names[reset_type]); return read_from_tree(prefix, argv + i, sha1, - quiet ? REFRESH_QUIET : REFRESH_SAY_CHANGED); + quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); } if (reset_type == NONE) reset_type = MIXED; /* by default */ @@ -302,7 +322,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) break; case MIXED: /* Report what has not been updated. */ update_index_refresh(0, NULL, - quiet ? REFRESH_QUIET : REFRESH_SAY_CHANGED); + quiet ? REFRESH_QUIET : REFRESH_IN_PORCELAIN); break; } diff --git a/builtin-send-pack.c b/builtin-send-pack.c index 47fb9f7ba..37e528e28 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -44,6 +44,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext NULL, NULL, NULL, + NULL, }; struct child_process po; int i; @@ -53,6 +54,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext argv[i++] = "--thin"; if (args->use_ofs_delta) argv[i++] = "--delta-base-offset"; + if (args->quiet) + argv[i++] = "-q"; memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; diff --git a/builtin-shortlog.c b/builtin-shortlog.c index 6a3812ee1..4d4a3c82d 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -56,7 +56,7 @@ static void insert_one_record(struct shortlog *log, /* copy author name to namebuf, to support matching on both name and email */ memcpy(namebuf, author, boemail - author); len = boemail - author; - while(len > 0 && isspace(namebuf[len-1])) + while (len > 0 && isspace(namebuf[len-1])) len--; namebuf[len] = 0; diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 01bea3b58..3510a86e3 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -6,8 +6,8 @@ #include "parse-options.h" static const char* show_branch_usage[] = { - "git show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base] [--topics] [--color] [<refs>...]", - "--reflog[=n[,b]] [--list] [--color] <branch>", + "git show-branch [-a|--all] [-r|--remotes] [--topo-order | --date-order] [--current] [--color | --no-color] [--sparse] [--more=<n> | --list | --independent | --merge-base] [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...", + "git show-branch (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]", NULL }; @@ -665,7 +665,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) OPT_BOOLEAN(0, "sha1-name", &sha1_name, "name commits with their object names"), OPT_BOOLEAN(0, "merge-base", &merge_base, - "act like git merge-base -a"), + "show possible merge bases"), OPT_BOOLEAN(0, "independent", &independent, "show refs unreachable from any other ref"), OPT_BOOLEAN(0, "topo-order", &lifo, diff --git a/builtin-tag.c b/builtin-tag.c index a51a6d1ea..c4790185e 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -390,7 +390,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), OPT_STRING('u', NULL, &keyid, "key-id", "use another key to sign the tag"), - OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"), + OPT_BOOLEAN('f', "force", &force, "replace the tag if exists"), OPT_GROUP("Tag listing options"), { diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 3d650a1c4..685566e0b 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -497,6 +497,8 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix) int i; unsigned char sha1[20]; + read_replace_refs = 0; + git_config(git_default_config, NULL); quiet = !isatty(2); diff --git a/builtin-update-server-info.c b/builtin-update-server-info.c new file mode 100644 index 000000000..2b3fddcc6 --- /dev/null +++ b/builtin-update-server-info.c @@ -0,0 +1,25 @@ +#include "cache.h" +#include "builtin.h" +#include "parse-options.h" + +static const char * const update_server_info_usage[] = { + "git update-server-info [--force]", + NULL +}; + +int cmd_update_server_info(int argc, const char **argv, const char *prefix) +{ + int force = 0; + struct option options[] = { + OPT_BOOLEAN('f', "force", &force, + "update the info files from scratch"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + update_server_info_usage, 0); + if (argc > 0) + usage_with_options(update_server_info_usage, options); + + return !!update_server_info(force); +} diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c index a18df04cf..b6079ae6c 100644 --- a/builtin-verify-pack.c +++ b/builtin-verify-pack.c @@ -2,13 +2,18 @@ #include "cache.h" #include "pack.h" #include "pack-revindex.h" +#include "parse-options.h" #define MAX_CHAIN 50 -static void show_pack_info(struct packed_git *p) +#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; @@ -31,16 +36,19 @@ static void show_pack_info(struct packed_git *p) type = packed_object_info_detail(p, offset, &size, &store_size, &delta_chain_length, base_sha1); - printf("%s ", sha1_to_hex(sha1)); + if (!stat_only) + printf("%s ", sha1_to_hex(sha1)); if (!delta_chain_length) { - printf("%-6s %lu %lu %"PRIuMAX"\n", - type, size, store_size, (uintmax_t)offset); + if (!stat_only) + printf("%-6s %lu %lu %"PRIuMAX"\n", + type, size, store_size, (uintmax_t)offset); baseobjects++; } else { - printf("%-6s %lu %lu %"PRIuMAX" %u %s\n", - type, size, store_size, (uintmax_t)offset, - delta_chain_length, sha1_to_hex(base_sha1)); + 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 @@ -65,10 +73,12 @@ static void show_pack_info(struct packed_git *p) chain_histogram[0] > 1 ? "s" : ""); } -static int verify_one_pack(const char *path, int verbose) +static int verify_one_pack(const char *path, unsigned int flags) { char arg[PATH_MAX]; int len; + int verbose = flags & VERIFY_PACK_VERBOSE; + int stat_only = flags & VERIFY_PACK_STAT_ONLY; struct packed_git *pack; int err; @@ -104,50 +114,53 @@ static int verify_one_pack(const char *path, int verbose) return error("packfile %s not found.", arg); install_packed_git(pack); - err = verify_pack(pack); - if (verbose) { + if (!stat_only) + err = verify_pack(pack); + else + err = open_pack_index(pack); + + if (verbose || stat_only) { if (err) printf("%s: bad\n", pack->pack_name); else { - show_pack_info(pack); - printf("%s: ok\n", pack->pack_name); + show_pack_info(pack, flags); + if (!stat_only) + printf("%s: ok\n", pack->pack_name); } } return err; } -static const char verify_pack_usage[] = "git verify-pack [-v] <pack>..."; +static const char * const verify_pack_usage[] = { + "git verify-pack [-v|--verbose] [-s|--stat-only] <pack>...", + NULL +}; int cmd_verify_pack(int argc, const char **argv, const char *prefix) { int err = 0; - int verbose = 0; - int no_more_options = 0; - int nothing_done = 1; + unsigned int flags = 0; + int i; + const struct option verify_pack_options[] = { + OPT_BIT('v', "verbose", &flags, "verbose", + VERIFY_PACK_VERBOSE), + OPT_BIT('s', "stat-only", &flags, "show statistics only", + VERIFY_PACK_STAT_ONLY), + OPT_END() + }; git_config(git_default_config, NULL); - while (1 < argc) { - if (!no_more_options && argv[1][0] == '-') { - if (!strcmp("-v", argv[1])) - verbose = 1; - else if (!strcmp("--", argv[1])) - no_more_options = 1; - else - usage(verify_pack_usage); - } - else { - if (verify_one_pack(argv[1], verbose)) - err = 1; - discard_revindex(); - nothing_done = 0; - } - argc--; argv++; + argc = parse_options(argc, argv, prefix, verify_pack_options, + verify_pack_usage, 0); + if (argc < 1) + usage_with_options(verify_pack_usage, verify_pack_options); + for (i = 0; i < argc; i++) { + if (verify_one_pack(argv[i], flags)) + err = 1; + discard_revindex(); } - if (nothing_done) - usage(verify_pack_usage); - return err; } diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c index 7f7fda42f..9f482c29f 100644 --- a/builtin-verify-tag.c +++ b/builtin-verify-tag.c @@ -10,9 +10,12 @@ #include "tag.h" #include "run-command.h" #include <signal.h> +#include "parse-options.h" -static const char builtin_verify_tag_usage[] = - "git verify-tag [-v|--verbose] <tag>..."; +static const char * const verify_tag_usage[] = { + "git verify-tag [-v|--verbose] <tag>...", + NULL +}; #define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" @@ -89,17 +92,17 @@ static int verify_tag(const char *name, int verbose) int cmd_verify_tag(int argc, const char **argv, const char *prefix) { int i = 1, verbose = 0, had_error = 0; + const struct option verify_tag_options[] = { + OPT__VERBOSE(&verbose), + OPT_END() + }; git_config(git_default_config, NULL); - if (argc > 1 && - (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose"))) { - verbose = 1; - i++; - } - + argc = parse_options(argc, argv, prefix, verify_tag_options, + verify_tag_usage, PARSE_OPT_KEEP_ARGV0); if (argc <= i) - usage(builtin_verify_tag_usage); + usage_with_options(verify_tag_usage, verify_tag_options); /* sometimes the program was terminated because this signal * was received in the process of writing the gpg input: */ diff --git a/builtin-write-tree.c b/builtin-write-tree.c index 3a24ce815..b223af416 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -7,9 +7,12 @@ #include "cache.h" #include "tree.h" #include "cache-tree.h" +#include "parse-options.h" -static const char write_tree_usage[] = -"git write-tree [--missing-ok] [--prefix=<prefix>/]"; +static const char * const write_tree_usage[] = { + "git write-tree [--missing-ok] [--prefix=<prefix>/]", + NULL +}; int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) { @@ -17,27 +20,22 @@ int cmd_write_tree(int argc, const char **argv, const char *unused_prefix) const char *prefix = NULL; unsigned char sha1[20]; const char *me = "git-write-tree"; + struct option write_tree_options[] = { + OPT_BIT(0, "missing-ok", &flags, "allow missing objects", + WRITE_TREE_MISSING_OK), + { OPTION_STRING, 0, "prefix", &prefix, "<prefix>/", + "write tree object for a subdirectory <prefix>" , + PARSE_OPT_LITERAL_ARGHELP }, + { OPTION_BIT, 0, "ignore-cache-tree", &flags, NULL, + "only useful for debugging", + PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, NULL, + WRITE_TREE_IGNORE_CACHE_TREE }, + OPT_END() + }; git_config(git_default_config, NULL); - while (1 < argc) { - const char *arg = argv[1]; - if (!strcmp(arg, "--missing-ok")) - flags |= WRITE_TREE_MISSING_OK; - else if (!prefixcmp(arg, "--prefix=")) - prefix = arg + 9; - else if (!prefixcmp(arg, "--ignore-cache-tree")) - /* - * This is only useful for debugging, so I - * do not bother documenting it. - */ - flags |= WRITE_TREE_IGNORE_CACHE_TREE; - else - usage(write_tree_usage); - argc--; argv++; - } - - if (argc > 2) - die("too many options"); + argc = parse_options(argc, argv, unused_prefix, write_tree_options, + write_tree_usage, 0); ret = write_cache_as_tree(sha1, flags, prefix); switch (ret) { @@ -13,7 +13,6 @@ extern const char git_more_info_string[]; extern void list_common_cmds_help(void); extern const char *help_unknown_cmd(const char *cmd); extern void prune_packed_objects(int); -extern int read_line_with_nul(char *buf, int size, FILE *file); extern int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out); extern int commit_tree(const char *msg, unsigned char *tree, @@ -103,6 +102,7 @@ extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); extern int cmd_update_index(int argc, const char **argv, const char *prefix); extern int cmd_update_ref(int argc, const char **argv, const char *prefix); +extern int cmd_update_server_info(int argc, const char **argv, const char *prefix); extern int cmd_upload_archive(int argc, const char **argv, const char *prefix); extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); extern int cmd_verify_tag(int argc, const char **argv, const char *prefix); @@ -112,5 +112,6 @@ extern int cmd_write_tree(int argc, const char **argv, const char *prefix); extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); extern int cmd_show_ref(int argc, const char **argv, const char *prefix); extern int cmd_pack_refs(int argc, const char **argv, const char *prefix); +extern int cmd_replace(int argc, const char **argv, const char *prefix); #endif @@ -234,7 +234,7 @@ int create_bundle(struct bundle_header *header, const char *path, rls.git_cmd = 1; if (start_command(&rls)) return -1; - rls_fout = fdopen(rls.out, "r"); + rls_fout = xfdopen(rls.out, "r"); while (fgets(buffer, sizeof(buffer), rls_fout)) { unsigned char sha1[20]; if (buffer[0] == '-') { @@ -4,6 +4,7 @@ #include "git-compat-util.h" #include "strbuf.h" #include "hash.h" +#include "advice.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@ -330,7 +331,7 @@ static inline void remove_name_hash(struct cache_entry *ce) #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path)) #define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags)) #define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags)) -#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL) +#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL) #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options)) #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options)) #define cache_name_exists(name, namelen, igncase) index_name_exists(&the_index, (name), (namelen), (igncase)) @@ -476,8 +477,8 @@ extern int check_path(const char *path, int len, struct stat *st, int skiplen); #define REFRESH_QUIET 0x0004 /* be quiet about it */ #define REFRESH_IGNORE_MISSING 0x0008 /* ignore non-existent */ #define REFRESH_IGNORE_SUBMODULES 0x0010 /* ignore submodules */ -#define REFRESH_SAY_CHANGED 0x0020 /* say "changed" not "needs update" */ -extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen); +#define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ +extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, char *header_msg); struct lock_file { struct lock_file *next; @@ -512,6 +513,7 @@ extern int log_all_ref_updates; extern int warn_ambiguous_refs; extern int shared_repository; extern const char *apply_default_whitespace; +extern const char *apply_default_ignorewhitespace; extern int zlib_compression_level; extern int core_compression_level; extern int core_compression_seen; @@ -519,6 +521,7 @@ extern size_t packed_git_window_size; extern size_t packed_git_limit; extern size_t delta_base_cache_limit; extern int auto_crlf; +extern int read_replace_refs; extern int fsync_object_files; extern int core_preload_index; @@ -655,7 +658,11 @@ char *strip_path_suffix(const char *path, const char *suffix); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); -extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size); +extern void *read_sha1_file_repl(const unsigned char *sha1, enum object_type *type, unsigned long *size, const unsigned char **replacement); +static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size) +{ + return read_sha1_file_repl(sha1, type, size, NULL); +} extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1); extern int write_sha1_file(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 *); @@ -725,9 +732,14 @@ enum date_mode { }; const char *show_date(unsigned long time, int timezone, enum date_mode mode); +const char *show_date_relative(unsigned long time, int tz, + const struct timeval *now, + char *timebuf, + size_t timebuf_size); int parse_date(const char *date, char *buf, int bufsize); void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); +unsigned long approxidate_relative(const char *date, const struct timeval *now); enum date_mode parse_date_format(const char *format); #define IDENT_WARN_ON_NO_NAME 1 @@ -912,13 +924,19 @@ extern const char *git_mailmap_file; extern void maybe_flush_or_die(FILE *, const char *); extern int copy_fd(int ifd, int ofd); extern int copy_file(const char *dst, const char *src, int mode); -extern ssize_t read_in_full(int fd, void *buf, size_t count); -extern ssize_t write_in_full(int fd, const void *buf, size_t count); +extern int copy_file_with_time(const char *dst, const char *src, int mode); extern void write_or_die(int fd, const void *buf, size_t count); extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg); extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg); extern void fsync_or_die(int fd, const char *); +extern ssize_t read_in_full(int fd, void *buf, size_t count); +extern ssize_t write_in_full(int fd, const void *buf, size_t count); +static inline ssize_t write_str_in_full(int fd, const char *str) +{ + return write_in_full(fd, str, strlen(str)); +} + /* pager.c */ extern void setup_pager(void); extern const char *pager_program; @@ -212,7 +212,7 @@ int write_shallow_commits(int fd, int use_pack_protocol) else { if (write_in_full(fd, hex, 40) != 40) break; - if (write_in_full(fd, "\n", 1) != 1) + if (write_str_in_full(fd, "\n") != 1) break; } } @@ -564,13 +564,13 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co while (interesting(list)) { struct commit *commit; struct commit_list *parents; - struct commit_list *n; + struct commit_list *next; int flags; commit = list->item; - n = list->next; + next = list->next; free(list); - list = n; + list = next; flags = commit->object.flags & (PARENT1 | PARENT2 | STALE); if (flags == (PARENT1 | PARENT2)) { @@ -598,11 +598,11 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co free_commit_list(list); list = result; result = NULL; while (list) { - struct commit_list *n = list->next; + struct commit_list *next = list->next; if (!(list->item->object.flags & STALE)) insert_by_date(list->item, &result); free(list); - list = n; + list = next; } return result; } @@ -123,6 +123,8 @@ struct commit_graft *read_graft_line(char *buf, int len); int register_commit_graft(struct commit_graft *, int); struct commit_graft *lookup_commit_graft(const unsigned char *sha1); +const unsigned char *lookup_replace_object(const unsigned char *sha1); + extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup); extern struct commit_list *get_octopus_merge_bases(struct commit_list *in); @@ -138,6 +140,8 @@ int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(int argc, const char **argv, const char *prefix); +extern int run_add_interactive(const char *revision, const char *patch_mode, + const char **pathspec); static inline int single_parent(struct commit *commit) { diff --git a/compat/bswap.h b/compat/bswap.h new file mode 100644 index 000000000..7246a12c6 --- /dev/null +++ b/compat/bswap.h @@ -0,0 +1,36 @@ +/* + * Let's make sure we always have a sane definition for ntohl()/htonl(). + * Some libraries define those as a function call, just to perform byte + * shifting, bringing significant overhead to what should be a simple + * operation. + */ + +/* + * Default version that the compiler ought to optimize properly with + * constant values. + */ +static inline unsigned int default_swab32(unsigned int val) +{ + return (((val & 0xff000000) >> 24) | + ((val & 0x00ff0000) >> 8) | + ((val & 0x0000ff00) << 8) | + ((val & 0x000000ff) << 24)); +} + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +#define bswap32(x) ({ \ + unsigned int __res; \ + if (__builtin_constant_p(x)) { \ + __res = default_swab32(x); \ + } else { \ + __asm__("bswap %0" : "=r" (__res) : "0" (x)); \ + } \ + __res; }) + +#undef ntohl +#undef htonl +#define ntohl(x) bswap32(x) +#define htonl(x) bswap32(x) + +#endif diff --git a/compat/mingw.c b/compat/mingw.c index bed417875..36ef8d321 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -824,7 +824,7 @@ void mingw_execvp(const char *cmd, char *const *argv) free_path_split(path); } -char **copy_environ() +static char **copy_environ(void) { char **env; int i = 0; @@ -861,7 +861,7 @@ static int lookup_env(char **env, const char *name, size_t nmln) /* * If name contains '=', then sets the variable, otherwise it unsets it */ -char **env_setenv(char **env, const char *name) +static char **env_setenv(char **env, const char *name) { char *eq = strchrnul(name, '='); int i = lookup_env(env, name, eq-name); @@ -886,6 +886,18 @@ char **env_setenv(char **env, const char *name) return env; } +/* + * Copies global environ and adjusts variables as specified by vars. + */ +char **make_augmented_environ(const char *const *vars) +{ + char **env = copy_environ(); + + while (*vars) + env = env_setenv(env, *vars++); + return env; +} + /* this is the first function to call into WS_32; initialize it */ #undef gethostbyname struct hostent *mingw_gethostbyname(const char *host) diff --git a/compat/mingw.h b/compat/mingw.h index c1859c548..c43917cd6 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -17,9 +17,10 @@ typedef int pid_t; #define S_IROTH 0 #define S_IXOTH 0 -#define WIFEXITED(x) ((unsigned)(x) < 259) /* STILL_ACTIVE */ +#define WIFEXITED(x) 1 +#define WIFSIGNALED(x) 0 #define WEXITSTATUS(x) ((x) & 0xff) -#define WIFSIGNALED(x) ((unsigned)(x) > 259) +#define WTERMSIG(x) SIGTERM #define SIGHUP 1 #define SIGQUIT 3 @@ -221,9 +222,8 @@ void mingw_open_html(const char *path); * helpers */ -char **copy_environ(void); +char **make_augmented_environ(const char *const *vars); void free_environ(char **env); -char **env_setenv(char **env, const char *name); /* * A replacement of main() that ensures that argv[0] has a path diff --git a/compat/snprintf.c b/compat/snprintf.c index 6c0fb056a..4d07087ab 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -3,7 +3,8 @@ /* * The size parameter specifies the available space, i.e. includes * the trailing NUL byte; but Windows's vsnprintf expects the - * number of characters to write without the trailing NUL. + * number of characters to write, and does not necessarily write the + * trailing NUL. */ #ifndef SNPRINTF_SIZE_CORR #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ < 4 @@ -627,6 +627,9 @@ int git_default_config(const char *var, const char *value, void *dummy) if (!prefixcmp(var, "mailmap.")) return git_default_mailmap_config(var, value); + if (!prefixcmp(var, "advice.")) + return git_default_advice_config(var, value); + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; @@ -1116,7 +1119,7 @@ int git_config_set_multivar(const char *key, const char *value, copy_end - copy_begin) goto write_err_out; if (new_line && - write_in_full(fd, "\n", 1) != 1) + write_str_in_full(fd, "\n") != 1) goto write_err_out; } copy_begin = store.offset[i]; diff --git a/configure.ac b/configure.ac index 3f1922d0b..b09b8e446 100644 --- a/configure.ac +++ b/configure.ac @@ -156,19 +156,11 @@ AC_MSG_NOTICE([CHECKS for site configuration]) # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. # -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. -# # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # -# Define ARM_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for ARM. -# # Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies MOZILLA_SHA1. +# This also implies BLK_SHA1. # # Define OPENSSLDIR=/foo/bar if your openssl header and library files are in # /foo/bar/include and /foo/bar/lib directories. @@ -513,7 +513,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, signal(SIGCHLD, SIG_DFL); host = strstr(url, "://"); - if(host) { + if (host) { *host = '\0'; protocol = get_protocol(url); host += 3; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 745b5fb78..2d2d5794a 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -318,13 +318,9 @@ __git_remotes () echo ${i#$d/remotes/} done [ "$ngoff" ] && shopt -u nullglob - for i in $(git --git-dir="$d" config --list); do - case "$i" in - remote.*.url=*) - i="${i#remote.}" - echo "${i/.url=*/}" - ;; - esac + for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do + i="${i#remote.}" + echo "${i/.url*/}" done } @@ -605,13 +601,9 @@ __git_porcelain_commandlist="$(__git_porcelain_commands 2>/dev/null)" __git_aliases () { local i IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --list); do - case "$i" in - alias.*) - i="${i#alias.}" - echo "${i/=*/}" - ;; - esac + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "alias\..*" 2>/dev/null); do + i="${i#alias.}" + echo "${i/ */}" done } @@ -674,6 +666,7 @@ _git_am () --*) __gitcomp " --3way --committer-date-is-author-date --ignore-date + --ignore-whitespace --ignore-space-change --interactive --keep --no-utf8 --signoff --utf8 --whitespace= " @@ -695,6 +688,7 @@ _git_apply () --stat --numstat --summary --check --index --cached --index-info --reverse --reject --unidiff-zero --apply --no-add --exclude= + --ignore-whitespace --ignore-space-change --whitespace= --inaccurate-eof --verbose " return @@ -1047,6 +1041,7 @@ _git_grep () --extended-regexp --basic-regexp --fixed-strings --files-with-matches --name-only --files-without-match + --max-depth --count --and --or --not --all-match " @@ -1529,13 +1524,14 @@ _git_config () url.*.*) local pfx="${cur%.*}." cur="${cur##*.}" - __gitcomp "insteadof" "$pfx" "$cur" + __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur" return ;; esac __gitcomp " add.ignore-errors alias. + apply.ignorewhitespace apply.whitespace branch.autosetupmerge branch.autosetuprebase @@ -1765,13 +1761,9 @@ _git_remote () ;; update) local i c='' IFS=$'\n' - for i in $(git --git-dir="$(__gitdir)" config --list); do - case "$i" in - remotes.*) - i="${i#remotes.}" - c="$c ${i/=*/}" - ;; - esac + for i in $(git --git-dir="$(__gitdir)" config --get-regexp "remotes\..*" 2>/dev/null); do + i="${i#remotes.}" + c="$c ${i/ */}" done __gitcomp "$c" ;; diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index eace9c18e..214930a02 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -429,16 +429,19 @@ Each entry is a cons of (SHORT-NAME . FULL-NAME)." (git-get-string-sha1 (git-call-process-string-display-error "write-tree")))) -(defun git-commit-tree (buffer tree head) - "Call git-commit-tree with buffer as input and return the resulting commit SHA1." +(defun git-commit-tree (buffer tree parent) + "Create a commit and possibly update HEAD. +Create a commit with the message in BUFFER using the tree with hash TREE. +Use PARENT as the parent of the new commit. If PARENT is the current \"HEAD\", +update the \"HEAD\" reference to the new commit." (let ((author-name (git-get-committer-name)) (author-email (git-get-committer-email)) (subject "commit (initial): ") author-date log-start log-end args coding-system-for-write) - (when head + (when parent (setq subject "commit: ") (push "-p" args) - (push head args)) + (push parent args)) (with-current-buffer buffer (goto-char (point-min)) (if @@ -474,7 +477,7 @@ Each entry is a cons of (SHORT-NAME . FULL-NAME)." (apply #'git-run-command-region buffer log-start log-end env "commit-tree" tree (nreverse args)))))) - (when commit (git-update-ref "HEAD" commit head subject)) + (when commit (git-update-ref "HEAD" commit parent subject)) commit))) (defun git-empty-db-p () @@ -1043,7 +1046,7 @@ The FILES list must be sorted." (defun git-add-file () "Add marked file(s) to the index cache." (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored)))) + (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored 'unmerged)))) ;; FIXME: add support for directories (unless files (push (file-relative-name (read-file-name "File to add: " nil nil t)) files)) @@ -1116,15 +1119,6 @@ The FILES list must be sorted." (when buffer (with-current-buffer buffer (revert-buffer t t t))))) (git-success-message "Reverted" names)))))) -(defun git-resolve-file () - "Resolve conflicts in marked file(s)." - (interactive) - (let ((files (git-get-filenames (git-marked-files-state 'unmerged)))) - (when files - (when (apply 'git-call-process-display-error "update-index" "--" files) - (git-update-status-files files) - (git-success-message "Resolved" files))))) - (defun git-remove-handled () "Remove handled files from the status list." (interactive) @@ -1553,7 +1547,6 @@ amended version of it." (define-key map "P" 'git-prev-unmerged-file) (define-key map "q" 'git-status-quit) (define-key map "r" 'git-remove-file) - (define-key map "R" 'git-resolve-file) (define-key map "t" toggle-map) (define-key map "T" 'git-toggle-all-marks) (define-key map "u" 'git-unmark-file) @@ -1595,7 +1588,6 @@ amended version of it." ("Merge" ["Next Unmerged File" git-next-unmerged-file t] ["Prev Unmerged File" git-prev-unmerged-file t] - ["Mark as Resolved" git-resolve-file t] ["Interactive Merge File" git-find-file-imerge t] ["Diff Against Common Base File" git-diff-file-base t] ["Diff Combined" git-diff-file-combined t] diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 342529db3..e710219ca 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -8,12 +8,10 @@ # License: MIT <http://www.opensource.org/licenses/mit-license.php> # -import optparse, sys, os, marshal, popen2, subprocess, shelve -import tempfile, getopt, sha, os.path, time, platform +import optparse, sys, os, marshal, subprocess, shelve +import tempfile, getopt, os.path, time, platform import re -from sets import Set; - verbose = False @@ -201,7 +199,7 @@ def isModeExec(mode): def isModeExecChanged(src_mode, dst_mode): return isModeExec(src_mode) != isModeExec(dst_mode) -def p4CmdList(cmd, stdin=None, stdin_mode='w+b'): +def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None): cmd = p4_build_cmd("-G %s" % (cmd)) if verbose: sys.stderr.write("Opening pipe: %s\n" % cmd) @@ -224,7 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b'): try: while True: entry = marshal.load(p4.stdout) - result.append(entry) + if cb is not None: + cb(entry) + else: + result.append(entry) except EOFError: pass exitCode = p4.wait() @@ -861,8 +862,8 @@ class P4Sync(Command): self.usage += " //depot/path[@revRange]" self.silent = False - self.createdBranches = Set() - self.committedChanges = Set() + self.createdBranches = set() + self.committedChanges = set() self.branch = "" self.detectBranches = False self.detectLabels = False @@ -950,10 +951,84 @@ class P4Sync(Command): return branches - ## Should move this out, doesn't use SELF. - def readP4Files(self, files): + # output one file from the P4 stream + # - helper for streamP4Files + + def streamOneP4File(self, file, contents): + if file["type"] == "apple": + print "\nfile %s is a strange apple file that forks. Ignoring" % \ + file['depotFile'] + return + + relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes) + if verbose: + sys.stderr.write("%s\n" % relPath) + + mode = "644" + if isP4Exec(file["type"]): + mode = "755" + elif file["type"] == "symlink": + mode = "120000" + # p4 print on a symlink contains "target\n", so strip it off + last = contents.pop() + last = last[:-1] + contents.append(last) + + if self.isWindows and file["type"].endswith("text"): + mangled = [] + for data in contents: + data = data.replace("\r\n", "\n") + mangled.append(data) + contents = mangled + + if file['type'] in ('text+ko', 'unicode+ko', 'binary+ko'): + contents = map(lambda text: re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text), contents) + elif file['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'): + contents = map(lambda text: re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text), contents) + + self.gitStream.write("M %s inline %s\n" % (mode, relPath)) + + # total length... + length = 0 + for d in contents: + length = length + len(d) + + self.gitStream.write("data %d\n" % length) + for d in contents: + self.gitStream.write(d) + self.gitStream.write("\n") + + def streamOneP4Deletion(self, file): + relPath = self.stripRepoPath(file['path'], self.branchPrefixes) + if verbose: + sys.stderr.write("delete %s\n" % relPath) + self.gitStream.write("D %s\n" % relPath) + + # handle another chunk of streaming data + def streamP4FilesCb(self, marshalled): + + if marshalled.has_key('depotFile') and self.stream_have_file_info: + # start of a new file - output the old one first + self.streamOneP4File(self.stream_file, self.stream_contents) + self.stream_file = {} + self.stream_contents = [] + self.stream_have_file_info = False + + # pick up the new file information... for the + # 'data' field we need to append to our array + for k in marshalled.keys(): + if k == 'data': + self.stream_contents.append(marshalled['data']) + else: + self.stream_file[k] = marshalled[k] + + self.stream_have_file_info = True + + # Stream directly from "p4 files" into "git fast-import" + def streamP4Files(self, files): filesForCommit = [] filesToRead = [] + filesToDelete = [] for f in files: includeFile = True @@ -967,50 +1042,35 @@ class P4Sync(Command): filesForCommit.append(f) if f['action'] not in ('delete', 'purge'): filesToRead.append(f) + else: + filesToDelete.append(f) - filedata = [] - if len(filesToRead) > 0: - filedata = p4CmdList('-x - print', - stdin='\n'.join(['%s#%s' % (f['path'], f['rev']) - for f in filesToRead]), - stdin_mode='w+') - - if "p4ExitCode" in filedata[0]: - die("Problems executing p4. Error: [%d]." - % (filedata[0]['p4ExitCode'])); - - j = 0; - contents = {} - while j < len(filedata): - stat = filedata[j] - j += 1 - text = '' - while j < len(filedata) and filedata[j]['code'] in ('text', 'unicode', 'binary'): - text += filedata[j]['data'] - del filedata[j]['data'] - j += 1 - - if not stat.has_key('depotFile'): - sys.stderr.write("p4 print fails with: %s\n" % repr(stat)) - continue + # deleted files... + for f in filesToDelete: + self.streamOneP4Deletion(f) - if stat['type'] in ('text+ko', 'unicode+ko', 'binary+ko'): - text = re.sub(r'(?i)\$(Id|Header):[^$]*\$',r'$\1$', text) - elif stat['type'] in ('text+k', 'ktext', 'kxtext', 'unicode+k', 'binary+k'): - text = re.sub(r'\$(Id|Header|Author|Date|DateTime|Change|File|Revision):[^$\n]*\$',r'$\1$', text) + if len(filesToRead) > 0: + self.stream_file = {} + self.stream_contents = [] + self.stream_have_file_info = False - contents[stat['depotFile']] = text + # curry self argument + def streamP4FilesCbSelf(entry): + self.streamP4FilesCb(entry) - for f in filesForCommit: - path = f['path'] - if contents.has_key(path): - f['data'] = contents[path] + p4CmdList("-x - print", + '\n'.join(['%s#%s' % (f['path'], f['rev']) + for f in filesToRead]), + cb=streamP4FilesCbSelf) - return filesForCommit + # do the last chunk + if self.stream_file.has_key('depotFile'): + self.streamOneP4File(self.stream_file, self.stream_contents) def commit(self, details, files, branch, branchPrefixes, parent = ""): epoch = details["time"] author = details["user"] + self.branchPrefixes = branchPrefixes if self.verbose: print "commit into %s" % branch @@ -1023,7 +1083,6 @@ class P4Sync(Command): new_files.append (f) else: sys.stderr.write("Ignoring file outside of prefix: %s\n" % path) - files = self.readP4Files(new_files) self.gitStream.write("commit %s\n" % branch) # gitStream.write("mark :%s\n" % details["change"]) @@ -1051,33 +1110,7 @@ class P4Sync(Command): print "parent %s" % parent self.gitStream.write("from %s\n" % parent) - for file in files: - if file["type"] == "apple": - print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path'] - continue - - relPath = self.stripRepoPath(file['path'], branchPrefixes) - if file["action"] in ("delete", "purge"): - self.gitStream.write("D %s\n" % relPath) - else: - data = file['data'] - - mode = "644" - if isP4Exec(file["type"]): - mode = "755" - elif file["type"] == "symlink": - mode = "120000" - # p4 print on a symlink contains "target\n", so strip it off - data = data[:-1] - - if self.isWindows and file["type"].endswith("text"): - data = data.replace("\r\n", "\n") - - self.gitStream.write("M %s inline %s\n" % (mode, relPath)) - self.gitStream.write("data %s\n" % len(data)) - self.gitStream.write(data) - self.gitStream.write("\n") - + self.streamP4Files(new_files) self.gitStream.write("\n") change = int(details["change"]) @@ -1627,7 +1660,7 @@ class P4Sync(Command): if len(self.changesFile) > 0: output = open(self.changesFile).readlines() - changeSet = Set() + changeSet = set() for line in output: changeSet.add(int(line)) diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl new file mode 100755 index 000000000..5782d80e2 --- /dev/null +++ b/contrib/fast-import/import-directories.perl @@ -0,0 +1,416 @@ +#!/usr/bin/perl -w +# +# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se> +# +# ------------------------------------------------------------------------ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# ------------------------------------------------------------------------ + +=pod + +=head1 NAME + +import-directories - Import bits and pieces to Git. + +=head1 SYNOPSIS + +B<import-directories.perl> F<configfile> F<outputfile> + +=head1 DESCRIPTION + +Script to import arbitrary projects version controlled by the "copy the +source directory to a new location and edit it there"-version controlled +projects into version control. Handles projects with arbitrary branching +and version trees, taking a file describing the inputs and generating a +file compatible with the L<git-fast-import(1)> format. + +=head1 CONFIGURATION FILE + +=head2 Format + +The configuration file is based on the standard I<.ini> format. + + ; Comments start with semi-colons + [section] + key=value + +Please see below for information on how to escape special characters. + +=head2 Global configuration + +Global configuration is done in the B<[config]> section, which should be +the first section in the file. Configuration can be changed by +repeating configuration sections later on. + + [config] + ; configure conversion of CRLFs. "convert" means that all CRLFs + ; should be converted into LFs (suitable for the core.autocrlf + ; setting set to true in Git). "none" means that all data is + ; treated as binary. + crlf=convert + +=head2 Revision configuration + +Each revision that is to be imported is described in three +sections. Revisions should be defined in topological order, so +that a revision's parent has always been defined when a new revision +is introduced. All the sections for one revision must be defined +before defining the next revision. + +Each revision is assigned a unique numerical identifier. The +numbers do not need to be consecutive, nor monotonically +increasing. + +For instance, if your configuration file contains only the two +revisions 4711 and 42, where 4711 is the initial commit, the +only requirement is that 4711 is completely defined before 42. + +=pod + +=head3 Revision description section + +A section whose section name is just an integer gives meta-data +about the revision. + + [3] + ; author sets the author of the revisions + author=Peter Krefting <peter@softwolves.pp.se> + ; branch sets the branch that the revision should be committed to + branch=master + ; parent describes the revision that is the parent of this commit + ; (optional) + parent=1 + ; merges describes a revision that is merged into this commit + ; (optional; can be repeated) + merges=2 + ; selects one file to take the timestamp from + ; (optional; if unspecified, the most recent file from the .files + ; section is used) + timestamp=3/source.c + +=head3 Revision contents section + +A section whose section name is an integer followed by B<.files> +describe all the files included in this revision. If a file that +was available previously is not included in this revision, it will +be removed. + +If an on-disk revision is incomplete, you can point to files from +a previous revision. There are no restriction as to where the source +files are located, nor to the names of them. + + [3.files] + ; the key is the path inside the repository, the value is the path + ; as seen from the importer script. + source.c=ver-3.00/source.c + source.h=ver-2.99/source.h + readme.txt=ver-3.00/introduction to the project.txt + +File names are treated as byte strings (but please see below on +quoting rules), and should be stored in the configuration file in +the encoding that should be used in the generated repository. + +=head3 Revision commit message section + +A section whose section name is an integer followed by B<.message> +gives the commit message. This section is read verbatim, up until +the beginning of the next section. As such, a commit message may not +contain a line that begins with an opening square bracket ("[") and +ends with a closing square bracket ("]"), unless they are surrounded +by whitespace or other characters. + + [3.message] + Implement foobar. + ; trailing blank lines are ignored. + +=cut + +# Globals +use strict; +use integer; +my $crlfmode = 0; +my @revs; +my (%revmap, %message, %files, %author, %branch, %parent, %merges, %time, %timesource); +my $sectiontype = 0; +my $rev = 0; +my $mark = 1; + +# Check command line +if ($#ARGV < 1 || $ARGV[0] =~ /^--?h/) +{ + exec('perldoc', $0); + exit 1; +} + +# Open configuration +my $config = $ARGV[0]; +open CFG, '<', $config or die "Cannot open configuration file \"$config\": "; + +# Open output +my $output = $ARGV[1]; +open OUT, '>', $output or die "Cannot create output file \"$output\": "; +binmode OUT; + +LINE: while (my $line = <CFG>) +{ + $line =~ s/\r?\n$//; + next LINE if $sectiontype != 4 && $line eq ''; + next LINE if $line =~ /^;/; + my $oldsectiontype = $sectiontype; + my $oldrev = $rev; + + # Sections + if ($line =~ m"^\[(config|(\d+)(|\.files|\.message))\]$") + { + if ($1 eq 'config') + { + $sectiontype = 1; + } + elsif ($3 eq '') + { + $sectiontype = 2; + $rev = $2; + # Create a new revision + die "Duplicate rev: $line\n " if defined $revmap{$rev}; + print "Reading revision $rev\n"; + push @revs, $rev; + $revmap{$rev} = $mark ++; + $time{$revmap{$rev}} = 0; + } + elsif ($3 eq '.files') + { + $sectiontype = 3; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + elsif ($3 eq '.message') + { + $sectiontype = 4; + $rev = $2; + die "Revision mismatch: $line\n " unless $rev == $oldrev; + } + else + { + die "Internal parse error: $line\n "; + } + next LINE; + } + + # Parse data + if ($sectiontype != 4) + { + # Key and value + if ($line =~ m"^\s*([^\s].*=.*[^\s])\s*$") + { + my ($key, $value) = &parsekeyvaluepair($1); + # Global configuration + if (1 == $sectiontype) + { + if ($key eq 'crlf') + { + $crlfmode = 1, next LINE if $value eq 'convert'; + $crlfmode = 0, next LINE if $value eq 'none'; + } + die "Unknown configuration option: $line\n "; + } + # Revision specification + if (2 == $sectiontype) + { + my $current = $revmap{$rev}; + $author{$current} = $value, next LINE if $key eq 'author'; + $branch{$current} = $value, next LINE if $key eq 'branch'; + $parent{$current} = $value, next LINE if $key eq 'parent'; + $timesource{$current} = $value, next LINE if $key eq 'timestamp'; + push(@{$merges{$current}}, $value), next LINE if $key eq 'merges'; + die "Unknown revision option: $line\n "; + } + # Filespecs + if (3 == $sectiontype) + { + # Add the file and create a marker + die "File not found: $line\n " unless -f $value; + my $current = $revmap{$rev}; + ${$files{$current}}{$key} = $mark; + my $time = &fileblob($value, $crlfmode, $mark ++); + + # Update revision timestamp if more recent than other + # files seen, or if this is the file we have selected + # to take the time stamp from using the "timestamp" + # directive. + if ((defined $timesource{$current} && $timesource{$current} eq $value) + || $time > $time{$current}) + { + $time{$current} = $time; + } + } + } + else + { + die "Parse error: $line\n "; + } + } + else + { + # Commit message + my $current = $revmap{$rev}; + if (defined $message{$current}) + { + $message{$current} .= "\n"; + } + $message{$current} .= $line; + } +} +close CFG; + +# Start spewing out data for git-fast-import +foreach my $commit (@revs) +{ + # Progress + print OUT "progress Creating revision $commit\n"; + + # Create commit header + my $mark = $revmap{$commit}; + + # Branch and commit id + print OUT "commit refs/heads/", $branch{$mark}, "\nmark :", $mark, "\n"; + + # Author and timestamp + die "No timestamp defined for $commit (no files?)\n" unless defined $time{$mark}; + print OUT "committer ", $author{$mark}, " ", $time{$mark}, " +0100\n"; + + # Commit message + die "No message defined for $commit\n" unless defined $message{$mark}; + my $message = $message{$mark}; + $message =~ s/\n$//; # Kill trailing empty line + print OUT "data ", length($message), "\n", $message, "\n"; + + # Parent and any merges + print OUT "from :", $revmap{$parent{$mark}}, "\n" if defined $parent{$mark}; + if (defined $merges{$mark}) + { + foreach my $merge (@{$merges{$mark}}) + { + print OUT "merge :", $revmap{$merge}, "\n"; + } + } + + # Output file marks + print OUT "deleteall\n"; # start from scratch + foreach my $file (sort keys %{$files{$mark}}) + { + print OUT "M 644 :", ${$files{$mark}}{$file}, " $file\n"; + } + print OUT "\n"; +} + +# Create one file blob +sub fileblob +{ + my ($filename, $crlfmode, $mark) = @_; + + # Import the file + print OUT "progress Importing $filename\nblob\nmark :$mark\n"; + open FILE, '<', $filename or die "Cannot read $filename\n "; + binmode FILE; + my ($size, $mtime) = (stat(FILE))[7,9]; + my $file; + read FILE, $file, $size; + close FILE; + $file =~ s/\r\n/\n/g if $crlfmode; + print OUT "data ", length($file), "\n", $file, "\n"; + + return $mtime; +} + +# Parse a key=value pair +sub parsekeyvaluepair +{ +=pod + +=head2 Escaping special characters + +Key and value strings may be enclosed in quotes, in which case +whitespace inside the quotes is preserved. Additionally, an equal +sign may be included in the key by preceeding it with a backslash. +For example: + + "key1 "=value1 + key2=" value2" + key\=3=value3 + key4=value=4 + "key5""=value5 + +Here the first key is "key1 " (note the trailing white-space) and the +second value is " value2" (note the leading white-space). The third +key contains an equal sign "key=3" and so does the fourth value, which +does not need to be escaped. The fifth key contains a trailing quote, +which does not need to be escaped since it is inside a surrounding +quote. + +=cut + my $pair = shift; + + # Separate key and value by the first non-quoted equal sign + my ($key, $value); + if ($pair =~ /^(.*[^\\])=(.*)$/) + { + ($key, $value) = ($1, $2) + } + else + { + die "Parse error: $pair\n "; + } + + # Unquote and unescape the key and value separately + return (&unescape($key), &unescape($value)); +} + +# Unquote and unescape +sub unescape +{ + my $string = shift; + + # First remove enclosing quotes. Backslash before the trailing + # quote leaves both. + if ($string =~ /^"(.*[^\\])"$/) + { + $string = $1; + } + + # Second remove any backslashes inside the unquoted string. + # For later: Handle special sequences like \t ? + $string =~ s/\\(.)/$1/g; + + return $string; +} + +__END__ + +=pod + +=head1 EXAMPLES + +B<import-directories.perl> F<project.import> + +=head1 AUTHOR + +Copyright 2008-2009 Peter Krefting E<lt>peter@softwolves.pp.se> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation. + +=cut diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 78e40d2a1..a90971668 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -8,9 +8,20 @@ ## perl import-tars.perl *.tar.bz2 ## git whatchanged import-tars ## +## Use --metainfo to specify the extension for a meta data file, where +## import-tars can read the commit message and optionally author and +## committer information. +## +## echo 'This is the commit message' > myfile.tar.bz2.msg +## perl import-tars.perl --metainfo=msg myfile.tar.bz2 use strict; -die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV; +use Getopt::Long; + +my $metaext = ''; + +die "usage: import-tars [--metainfo=extension] *.tar.{gz,bz2,Z}\n" + unless GetOptions('metainfo=s' => \$metaext) && @ARGV; my $branch_name = 'import-tars'; my $branch_ref = "refs/heads/$branch_name"; @@ -109,12 +120,43 @@ foreach my $tar_file (@ARGV) $have_top_dir = 0 if $top_dir ne $1; } + my $commit_msg = "Imported from $tar_file."; + my $this_committer_name = $committer_name; + my $this_committer_email = $committer_email; + my $this_author_name = $author_name; + my $this_author_email = $author_email; + if ($metaext ne '') { + # Optionally read a commit message from <filename.tar>.msg + # Add a line on the form "Committer: name <e-mail>" to override + # the committer and "Author: name <e-mail>" to override the + # author for this tar ball. + if (open MSG, '<', "${tar_file}.${metaext}") { + my $header_done = 0; + $commit_msg = ''; + while (<MSG>) { + if (!$header_done && /^Committer:\s+([^<>]*)\s+<(.*)>\s*$/i) { + $this_committer_name = $1; + $this_committer_email = $2; + } elsif (!$header_done && /^Author:\s+([^<>]*)\s+<(.*)>\s*$/i) { + $this_author_name = $1; + $this_author_email = $2; + } elsif (!$header_done && /^$/ { # empty line ends header. + $header_done = 1; + } else { + $commit_msg .= $_; + $header_done = 1; + } + } + close MSG; + } + } + print FI <<EOF; commit $branch_ref -author $author_name <$author_email> $author_time +0000 -committer $committer_name <$committer_email> $commit_time +0000 +author $this_author_name <$this_author_email> $author_time +0000 +committer $this_committer_name <$this_committer_email> $commit_time +0000 data <<END_OF_COMMIT_MESSAGE -Imported from $tar_file. +$commit_msg END_OF_COMMIT_MESSAGE deleteall @@ -267,7 +267,7 @@ static int filter_buffer(int fd, void *data) status = finish_command(&child_process); if (status) - error("external filter %s failed %d", params->cmd, -status); + error("external filter %s failed %d", params->cmd, status); return (write_err || status); } @@ -35,6 +35,19 @@ int copy_fd(int ifd, int ofd) return 0; } +static int copy_times(const char *dst, const char *src) +{ + struct stat st; + struct utimbuf times; + if (stat(src, &st) < 0) + return -1; + times.actime = st.st_atime; + times.modtime = st.st_mtime; + if (utime(dst, ×) < 0) + return -1; + return 0; +} + int copy_file(const char *dst, const char *src, int mode) { int fdi, fdo, status; @@ -55,3 +68,11 @@ int copy_file(const char *dst, const char *src, int mode) return status; } + +int copy_file_with_time(const char *dst, const char *src, int mode) +{ + int status = copy_file(dst, src, mode); + if (!status) + return copy_times(dst, src); + return status; +} @@ -24,6 +24,8 @@ time_t tm_to_time_t(const struct tm *tm) return -1; if (month < 2 || (year + 2) % 4) day--; + if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0) + return -1; return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL + tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec; } @@ -84,6 +86,67 @@ static int local_tzoffset(unsigned long time) return offset * eastwest; } +const char *show_date_relative(unsigned long time, int tz, + const struct timeval *now, + char *timebuf, + size_t timebuf_size) +{ + unsigned long diff; + if (now->tv_sec < time) + return "in the future"; + diff = now->tv_sec - time; + if (diff < 90) { + snprintf(timebuf, timebuf_size, "%lu seconds ago", diff); + return timebuf; + } + /* Turn it into minutes */ + diff = (diff + 30) / 60; + if (diff < 90) { + snprintf(timebuf, timebuf_size, "%lu minutes ago", diff); + return timebuf; + } + /* Turn it into hours */ + diff = (diff + 30) / 60; + if (diff < 36) { + snprintf(timebuf, timebuf_size, "%lu hours ago", diff); + return timebuf; + } + /* We deal with number of days from here on */ + diff = (diff + 12) / 24; + if (diff < 14) { + snprintf(timebuf, timebuf_size, "%lu days ago", diff); + return timebuf; + } + /* Say weeks for the past 10 weeks or so */ + if (diff < 70) { + snprintf(timebuf, timebuf_size, "%lu weeks ago", (diff + 3) / 7); + return timebuf; + } + /* Say months for the past 12 months or so */ + if (diff < 360) { + snprintf(timebuf, timebuf_size, "%lu months ago", (diff + 15) / 30); + return timebuf; + } + /* Give years and months for 5 years or so */ + if (diff < 1825) { + unsigned long years = diff / 365; + unsigned long months = (diff % 365 + 15) / 30; + int n; + n = snprintf(timebuf, timebuf_size, "%lu year%s", + years, (years > 1 ? "s" : "")); + if (months) + snprintf(timebuf + n, timebuf_size - n, + ", %lu month%s ago", + months, (months > 1 ? "s" : "")); + else + snprintf(timebuf + n, timebuf_size - n, " ago"); + return timebuf; + } + /* Otherwise, just years. Centuries is probably overkill. */ + snprintf(timebuf, timebuf_size, "%lu years ago", (diff + 183) / 365); + return timebuf; +} + const char *show_date(unsigned long time, int tz, enum date_mode mode) { struct tm *tm; @@ -95,63 +158,10 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode) } if (mode == DATE_RELATIVE) { - unsigned long diff; struct timeval now; gettimeofday(&now, NULL); - if (now.tv_sec < time) - return "in the future"; - diff = now.tv_sec - time; - if (diff < 90) { - snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff); - return timebuf; - } - /* Turn it into minutes */ - diff = (diff + 30) / 60; - if (diff < 90) { - snprintf(timebuf, sizeof(timebuf), "%lu minutes ago", diff); - return timebuf; - } - /* Turn it into hours */ - diff = (diff + 30) / 60; - if (diff < 36) { - snprintf(timebuf, sizeof(timebuf), "%lu hours ago", diff); - return timebuf; - } - /* We deal with number of days from here on */ - diff = (diff + 12) / 24; - if (diff < 14) { - snprintf(timebuf, sizeof(timebuf), "%lu days ago", diff); - return timebuf; - } - /* Say weeks for the past 10 weeks or so */ - if (diff < 70) { - snprintf(timebuf, sizeof(timebuf), "%lu weeks ago", (diff + 3) / 7); - return timebuf; - } - /* Say months for the past 12 months or so */ - if (diff < 360) { - snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30); - return timebuf; - } - /* Give years and months for 5 years or so */ - if (diff < 1825) { - unsigned long years = diff / 365; - unsigned long months = (diff % 365 + 15) / 30; - int n; - n = snprintf(timebuf, sizeof(timebuf), "%lu year%s", - years, (years > 1 ? "s" : "")); - if (months) - snprintf(timebuf + n, sizeof(timebuf) - n, - ", %lu month%s ago", - months, (months > 1 ? "s" : "")); - else - snprintf(timebuf + n, sizeof(timebuf) - n, - " ago"); - return timebuf; - } - /* Otherwise, just years. Centuries is probably overkill. */ - snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365); - return timebuf; + return show_date_relative(time, tz, &now, + timebuf, sizeof(timebuf)); } if (mode == DATE_LOCAL) @@ -425,13 +435,19 @@ static int match_multi_number(unsigned long num, char c, const char *date, char return end - date; } -/* Have we filled in any part of the time/date yet? */ +/* + * Have we filled in any part of the time/date yet? + * We just do a binary 'and' to see if the sign bit + * is set in all the values. + */ static inline int nodate(struct tm *tm) { - return tm->tm_year < 0 && - tm->tm_mon < 0 && - tm->tm_mday < 0 && - !(tm->tm_hour | tm->tm_min | tm->tm_sec); + return (tm->tm_year & + tm->tm_mon & + tm->tm_mday & + tm->tm_hour & + tm->tm_min & + tm->tm_sec) < 0; } /* @@ -525,11 +541,8 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt } } - if (num > 0 && num < 32) { - tm->tm_mday = num; - } else if (num > 0 && num < 13) { + if (num > 0 && num < 13 && tm->tm_mon < 0) tm->tm_mon = num-1; - } return n; } @@ -583,6 +596,9 @@ int parse_date(const char *date, char *result, int maxlen) tm.tm_mon = -1; tm.tm_mday = -1; tm.tm_isdst = -1; + tm.tm_hour = -1; + tm.tm_min = -1; + tm.tm_sec = -1; offset = -1; tm_gmt = 0; @@ -657,42 +673,59 @@ void datestamp(char *buf, int bufsize) date_string(now, offset, buf, bufsize); } -static void update_tm(struct tm *tm, unsigned long sec) +/* + * Relative time update (eg "2 days ago"). If we haven't set the time + * yet, we need to set it from current time. + */ +static unsigned long update_tm(struct tm *tm, struct tm *now, unsigned long sec) { - time_t n = mktime(tm) - sec; + time_t n; + + if (tm->tm_mday < 0) + tm->tm_mday = now->tm_mday; + if (tm->tm_mon < 0) + tm->tm_mon = now->tm_mon; + if (tm->tm_year < 0) { + tm->tm_year = now->tm_year; + if (tm->tm_mon > now->tm_mon) + tm->tm_year--; + } + + n = mktime(tm) - sec; localtime_r(&n, tm); + return n; } -static void date_yesterday(struct tm *tm, int *num) +static void date_yesterday(struct tm *tm, struct tm *now, int *num) { - update_tm(tm, 24*60*60); + update_tm(tm, now, 24*60*60); } -static void date_time(struct tm *tm, int hour) +static void date_time(struct tm *tm, struct tm *now, int hour) { if (tm->tm_hour < hour) - date_yesterday(tm, NULL); + date_yesterday(tm, now, NULL); tm->tm_hour = hour; tm->tm_min = 0; tm->tm_sec = 0; } -static void date_midnight(struct tm *tm, int *num) +static void date_midnight(struct tm *tm, struct tm *now, int *num) { - date_time(tm, 0); + date_time(tm, now, 0); } -static void date_noon(struct tm *tm, int *num) +static void date_noon(struct tm *tm, struct tm *now, int *num) { - date_time(tm, 12); + date_time(tm, now, 12); } -static void date_tea(struct tm *tm, int *num) +static void date_tea(struct tm *tm, struct tm *now, int *num) { - date_time(tm, 17); + date_time(tm, now, 17); } -static void date_pm(struct tm *tm, int *num) +static void date_pm(struct tm *tm, struct tm *now, int *num) { int hour, n = *num; *num = 0; @@ -706,7 +739,7 @@ static void date_pm(struct tm *tm, int *num) tm->tm_hour = (hour % 12) + 12; } -static void date_am(struct tm *tm, int *num) +static void date_am(struct tm *tm, struct tm *now, int *num) { int hour, n = *num; *num = 0; @@ -720,7 +753,7 @@ static void date_am(struct tm *tm, int *num) tm->tm_hour = (hour % 12); } -static void date_never(struct tm *tm, int *num) +static void date_never(struct tm *tm, struct tm *now, int *num) { time_t n = 0; localtime_r(&n, tm); @@ -728,7 +761,7 @@ static void date_never(struct tm *tm, int *num) static const struct special { const char *name; - void (*fn)(struct tm *, int *); + void (*fn)(struct tm *, struct tm *, int *); } special[] = { { "yesterday", date_yesterday }, { "noon", date_noon }, @@ -757,7 +790,7 @@ static const struct typelen { { NULL } }; -static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) +static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num) { const struct typelen *tl; const struct special *s; @@ -778,7 +811,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) for (s = special; s->name; s++) { int len = strlen(s->name); if (match_string(date, s->name) == len) { - s->fn(tm, num); + s->fn(tm, now, num); return end; } } @@ -800,7 +833,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) while (tl->type) { int len = strlen(tl->type); if (match_string(date, tl->type) >= len-1) { - update_tm(tm, tl->length * *num); + update_tm(tm, now, tl->length * *num); *num = 0; return end; } @@ -818,13 +851,15 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) n++; diff += 7*n; - update_tm(tm, diff * 24 * 60 * 60); + update_tm(tm, now, diff * 24 * 60 * 60); return end; } } if (match_string(date, "months") >= 5) { - int n = tm->tm_mon - *num; + int n; + update_tm(tm, now, 0); /* fill in date fields if needed */ + n = tm->tm_mon - *num; *num = 0; while (n < 0) { n += 12; @@ -835,6 +870,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, int *num) } if (match_string(date, "years") >= 4) { + update_tm(tm, now, 0); /* fill in date fields if needed */ tm->tm_year -= *num; *num = 0; return end; @@ -866,36 +902,82 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) return end; } -unsigned long approxidate(const char *date) +/* + * Do we have a pending number at the end, or when + * we see a new one? Let's assume it's a month day, + * as in "Dec 6, 1992" + */ +static void pending_number(struct tm *tm, int *num) +{ + int number = *num; + + if (number) { + *num = 0; + if (tm->tm_mday < 0 && number < 32) + tm->tm_mday = number; + else if (tm->tm_mon < 0 && number < 13) + tm->tm_mon = number-1; + else if (tm->tm_year < 0) { + if (number > 1969 && number < 2100) + tm->tm_year = number - 1900; + else if (number > 69 && number < 100) + tm->tm_year = number; + else if (number < 38) + tm->tm_year = 100 + number; + /* We screw up for number = 00 ? */ + } + } +} + +static unsigned long approxidate_str(const char *date, const struct timeval *tv) { int number = 0; struct tm tm, now; - struct timeval tv; time_t time_sec; - char buffer[50]; - - if (parse_date(date, buffer, sizeof(buffer)) > 0) - return strtoul(buffer, NULL, 10); - gettimeofday(&tv, NULL); - time_sec = tv.tv_sec; + time_sec = tv->tv_sec; localtime_r(&time_sec, &tm); now = tm; + + tm.tm_year = -1; + tm.tm_mon = -1; + tm.tm_mday = -1; + for (;;) { unsigned char c = *date; if (!c) break; date++; if (isdigit(c)) { + pending_number(&tm, &number); date = approxidate_digit(date-1, &tm, &number); continue; } if (isalpha(c)) - date = approxidate_alpha(date-1, &tm, &number); + date = approxidate_alpha(date-1, &tm, &now, &number); } - if (number > 0 && number < 32) - tm.tm_mday = number; - if (tm.tm_mon > now.tm_mon && tm.tm_year == now.tm_year) - tm.tm_year--; - return mktime(&tm); + pending_number(&tm, &number); + return update_tm(&tm, &now, 0); +} + +unsigned long approxidate_relative(const char *date, const struct timeval *tv) +{ + char buffer[50]; + + if (parse_date(date, buffer, sizeof(buffer)) > 0) + return strtoul(buffer, NULL, 0); + + return approxidate_str(date, tv); +} + +unsigned long approxidate(const char *date) +{ + struct timeval tv; + char buffer[50]; + + if (parse_date(date, buffer, sizeof(buffer)) > 0) + return strtoul(buffer, NULL, 0); + + gettimeofday(&tv, NULL); + return approxidate_str(date, &tv); } diff --git a/diff-delta.c b/diff-delta.c index a4e28df71..464ac3ffc 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -4,7 +4,7 @@ * This code was greatly inspired by parts of LibXDiff from Davide Libenzi * http://www.xmailserver.org/xdiff-lib.html * - * Rewritten for GIT by Nicolas Pitre <nico@cam.org>, (C) 2005-2007 + * Rewritten for GIT by Nicolas Pitre <nico@fluxnic.net>, (C) 2005-2007 * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde7..0c74ef5cb 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -162,7 +162,8 @@ int run_diff_files(struct rev_info *revs, unsigned int option) if (ce_uptodate(ce)) continue; - changed = check_removed(ce, &st); + /* If CE_VALID is set, don't look at workdir for file removal */ + changed = (ce->ce_flags & CE_VALID) ? 0 : check_removed(ce, &st); if (changed) { if (changed < 0) { perror(ce->name); @@ -309,22 +310,6 @@ static int show_modified(struct rev_info *revs, } /* - * This turns all merge entries into "stage 3". That guarantees that - * when we read in the new tree (into "stage 1"), we won't lose sight - * of the fact that we had unmerged entries. - */ -static void mark_merge_entries(void) -{ - int i; - for (i = 0; i < active_nr; i++) { - struct cache_entry *ce = active_cache[i]; - if (!ce_stage(ce)) - continue; - ce->ce_flags |= CE_STAGEMASK; - } -} - -/* * This gets a mix of an existing index and a tree, one pathname entry * at a time. The index entry may be a single stage-0 one, but it could * also be multiple unmerged entries (in which case idx_pos/idx_nr will @@ -337,6 +322,8 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct rev_info *revs = o->unpack_data; int match_missing, cached; + /* if the entry is not checked out, don't examine work tree */ + cached = o->index_only || (idx && (idx->ce_flags & CE_VALID)); /* * Backward compatibility wart - "diff-index -m" does * not mean "do not ignore merges", but "match_missing". @@ -344,12 +331,11 @@ static void do_oneway_diff(struct unpack_trees_options *o, * But with the revision flag parsing, that's found in * "!revs->ignore_merges". */ - cached = o->index_only; match_missing = !revs->ignore_merges; if (cached && idx && ce_stage(idx)) { - if (tree) - diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, idx->sha1); + diff_unmerge(&revs->diffopt, idx->name, idx->ce_mode, + idx->sha1); return; } @@ -435,8 +421,6 @@ int run_diff_index(struct rev_info *revs, int cached) struct unpack_trees_options opts; struct tree_desc t; - mark_merge_entries(); - ent = revs->pending.objects[0].item; tree_name = revs->pending.objects[0].name; tree = parse_tree_indirect(ent->sha1); @@ -2691,7 +2691,7 @@ static int parse_num(const char **cp_p) num = 0; scale = 1; dot = 0; - for(;;) { + for (;;) { ch = *cp; if ( !dot && ch == '.' ) { scale = 1; diff --git a/environment.c b/environment.c index 8f5eaa7dd..5de683784 100644 --- a/environment.c +++ b/environment.c @@ -26,6 +26,7 @@ const char *git_commit_encoding; const char *git_log_output_encoding; int shared_repository = PERM_UMASK; const char *apply_default_whitespace; +const char *apply_default_ignorewhitespace; int zlib_compression_level = Z_BEST_SPEED; int core_compression_level; int core_compression_seen; @@ -38,6 +39,7 @@ int pager_use_color = 1; const char *editor_program; const char *excludes_file; int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */ +int read_replace_refs = 1; enum safe_crlf safe_crlf = SAFE_CRLF_WARN; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 06f70602c..392efb913 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -72,6 +72,79 @@ sub colored { # command line options my $patch_mode; +my $patch_mode_revision; + +sub apply_patch; +sub apply_patch_for_checkout_commit; +sub apply_patch_for_stash; + +my %patch_modes = ( + 'stage' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stage', + TARGET => '', + PARTICIPLE => 'staging', + FILTER => 'file-only', + }, + 'stash' => { + DIFF => 'diff-index -p HEAD', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Stash', + TARGET => '', + PARTICIPLE => 'stashing', + FILTER => undef, + }, + 'reset_head' => { + DIFF => 'diff-index -p --cached', + APPLY => sub { apply_patch 'apply -R --cached', @_; }, + APPLY_CHECK => 'apply -R --cached', + VERB => 'Unstage', + TARGET => '', + PARTICIPLE => 'unstaging', + FILTER => 'index-only', + }, + 'reset_nothead' => { + DIFF => 'diff-index -R -p --cached', + APPLY => sub { apply_patch 'apply --cached', @_; }, + APPLY_CHECK => 'apply --cached', + VERB => 'Apply', + TARGET => ' to index', + PARTICIPLE => 'applying', + FILTER => 'index-only', + }, + 'checkout_index' => { + DIFF => 'diff-files -p', + APPLY => sub { apply_patch 'apply -R', @_; }, + APPLY_CHECK => 'apply -R', + VERB => 'Discard', + TARGET => ' from worktree', + PARTICIPLE => 'discarding', + FILTER => 'file-only', + }, + 'checkout_head' => { + DIFF => 'diff-index -p', + APPLY => sub { apply_patch_for_checkout_commit '-R', @_ }, + APPLY_CHECK => 'apply -R', + VERB => 'Discard', + TARGET => ' from index and worktree', + PARTICIPLE => 'discarding', + FILTER => undef, + }, + 'checkout_nothead' => { + DIFF => 'diff-index -R -p', + APPLY => sub { apply_patch_for_checkout_commit '', @_ }, + APPLY_CHECK => 'apply', + VERB => 'Apply', + TARGET => ' to index and worktree', + PARTICIPLE => 'applying', + FILTER => undef, + }, +); + +my %patch_mode_flavour = %{$patch_modes{stage}}; sub run_cmd_pipe { if ($^O eq 'MSWin32' || $^O eq 'msys') { @@ -190,7 +263,14 @@ sub list_modified { return if (!@tracked); } - my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD'; + my $reference; + if (defined $patch_mode_revision and $patch_mode_revision ne 'HEAD') { + $reference = $patch_mode_revision; + } elsif (is_initial_commit()) { + $reference = get_empty_tree(); + } else { + $reference = 'HEAD'; + } for (run_cmd_pipe(qw(git diff-index --cached --numstat --summary), $reference, '--', @tracked)) { @@ -613,12 +693,24 @@ sub add_untracked_cmd { print "\n"; } +sub run_git_apply { + my $cmd = shift; + my $fh; + open $fh, '| git ' . $cmd; + print $fh @_; + return close $fh; +} + sub parse_diff { my ($path) = @_; - my @diff = run_cmd_pipe(qw(git diff-files -p --), $path); + my @diff_cmd = split(" ", $patch_mode_flavour{DIFF}); + if (defined $patch_mode_revision) { + push @diff_cmd, $patch_mode_revision; + } + my @diff = run_cmd_pipe("git", @diff_cmd, "--", $path); my @colored = (); if ($diff_use_color) { - @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path); + @colored = run_cmd_pipe("git", @diff_cmd, qw(--color --), $path); } my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; @@ -881,6 +973,7 @@ sub edit_hunk_manually { or die "failed to open hunk edit file for writing: " . $!; print $fh "# Manual hunk edit mode -- see bottom for a quick guide\n"; print $fh @$oldtext; + my $participle = $patch_mode_flavour{PARTICIPLE}; print $fh <<EOF; # --- # To remove '-' lines, make them ' ' lines (context). @@ -888,7 +981,7 @@ sub edit_hunk_manually { # Lines starting with # will be removed. # # If the patch applies cleanly, the edited hunk will immediately be -# marked for staging. If it does not apply cleanly, you will be given +# marked for $participle. If it does not apply cleanly, you will be given # an opportunity to edit again. If all lines of the hunk are removed, # then the edit is aborted and the hunk is left unchanged. EOF @@ -922,11 +1015,8 @@ EOF sub diff_applies { my $fh; - open $fh, '| git apply --recount --cached --check'; - for my $h (@_) { - print $fh @{$h->{TEXT}}; - } - return close $fh; + return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --recount --check', + map { @{$_->{TEXT}} } @_); } sub _restore_terminal_and_die { @@ -992,12 +1082,14 @@ sub edit_hunk_loop { } sub help_patch_cmd { - print colored $help_color, <<\EOF ; -y - stage this hunk -n - do not stage this hunk -q - quit, do not stage this hunk nor any of the remaining ones -a - stage this and all the remaining hunks in the file -d - do not stage this hunk nor any of the remaining hunks in the file + my $verb = lc $patch_mode_flavour{VERB}; + my $target = $patch_mode_flavour{TARGET}; + print colored $help_color, <<EOF ; +y - $verb this hunk$target +n - do not $verb this hunk$target +q - quit, do not $verb this hunk nor any of the remaining ones +a - $verb this and all the remaining hunks in the file +d - do not $verb this hunk nor any of the remaining hunks in the file g - select a hunk to go to / - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk @@ -1010,8 +1102,40 @@ e - manually edit the current hunk EOF } +sub apply_patch { + my $cmd = shift; + my $ret = run_git_apply $cmd . ' --recount', @_; + if (!$ret) { + print STDERR @_; + } + return $ret; +} + +sub apply_patch_for_checkout_commit { + my $reverse = shift; + my $applies_index = run_git_apply 'apply '.$reverse.' --cached --recount --check', @_; + my $applies_worktree = run_git_apply 'apply '.$reverse.' --recount --check', @_; + + if ($applies_worktree && $applies_index) { + run_git_apply 'apply '.$reverse.' --cached --recount', @_; + run_git_apply 'apply '.$reverse.' --recount', @_; + return 1; + } elsif (!$applies_index) { + print colored $error_color, "The selected hunks do not apply to the index!\n"; + if (prompt_yesno "Apply them to the worktree anyway? ") { + return run_git_apply 'apply '.$reverse.' --recount', @_; + } else { + print colored $error_color, "Nothing was applied.\n"; + return 0; + } + } else { + print STDERR @_; + return 0; + } +} + sub patch_update_cmd { - my @all_mods = list_modified('file-only'); + my @all_mods = list_modified($patch_mode_flavour{FILTER}); my @mods = grep { !($_->{BINARY}) } @all_mods; my @them; @@ -1142,8 +1266,9 @@ sub patch_update_file { for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, 'Stage ', - ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'), + print colored $prompt_color, $patch_mode_flavour{VERB}, + ($hunk[$ix]{TYPE} eq 'mode' ? ' mode change' : ' this hunk'), + $patch_mode_flavour{TARGET}, " [y,n,q,a,d,/$other,?]? "; my $line = prompt_single_character; if ($line) { @@ -1317,16 +1442,9 @@ sub patch_update_file { if (@result) { my $fh; - - open $fh, '| git apply --cached --recount'; - for (@{$head->{TEXT}}, @result) { - print $fh $_; - } - if (!close $fh) { - for (@{$head->{TEXT}}, @result) { - print STDERR $_; - } - } + my @patch = (@{$head->{TEXT}}, @result); + my $apply_routine = $patch_mode_flavour{APPLY}; + &$apply_routine(@patch); refresh(); } @@ -1367,11 +1485,41 @@ EOF sub process_args { return unless @ARGV; my $arg = shift @ARGV; - if ($arg eq "--patch") { - $patch_mode = 1; - $arg = shift @ARGV or die "missing --"; + if ($arg =~ /--patch(?:=(.*))?/) { + if (defined $1) { + if ($1 eq 'reset') { + $patch_mode = 'reset_head'; + $patch_mode_revision = 'HEAD'; + $arg = shift @ARGV or die "missing --"; + if ($arg ne '--') { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'reset_head' : 'reset_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'checkout') { + $arg = shift @ARGV or die "missing --"; + if ($arg eq '--') { + $patch_mode = 'checkout_index'; + } else { + $patch_mode_revision = $arg; + $patch_mode = ($arg eq 'HEAD' ? + 'checkout_head' : 'checkout_nothead'); + $arg = shift @ARGV or die "missing --"; + } + } elsif ($1 eq 'stage' or $1 eq 'stash') { + $patch_mode = $1; + $arg = shift @ARGV or die "missing --"; + } else { + die "unknown --patch mode: $1"; + } + } else { + $patch_mode = 'stage'; + $arg = shift @ARGV or die "missing --"; + } die "invalid argument $arg, expecting --" unless $arg eq "--"; + %patch_mode_flavour = %{$patch_modes{$patch_mode}}; } elsif ($arg ne "--") { die "invalid argument $arg, expecting --"; @@ -15,7 +15,10 @@ q,quiet be quiet s,signoff add a Signed-off-by line to the commit message u,utf8 recode into utf8 (default) k,keep pass -k flag to git-mailinfo +c,scissors strip everything before a scissors line whitespace= pass it through git-apply +ignore-space-change pass it through git-apply +ignore-whitespace pass it through git-apply directory= pass it through git-apply C= pass it through git-apply p= pass it through git-apply @@ -211,7 +214,13 @@ check_patch_format () { split_patches () { case "$patch_format" in mbox) - git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" || + case "$rebasing" in + '') + keep_cr= ;; + ?*) + keep_cr=--keep-cr ;; + esac + git mailsplit -d"$prec" -o"$dotest" -b $keep_cr -- "$@" > "$dotest/last" || clean_abort ;; stgit-series) @@ -280,7 +289,7 @@ split_patches () { prec=4 dotest="$GIT_DIR/rebase-apply" sign= utf8=t keep= skip= interactive= resolved= rebasing= abort= -resolvemsg= resume= +resolvemsg= resume= scissors= git_apply_opt= committer_date_is_author_date= ignore_date= @@ -302,6 +311,10 @@ do utf8= ;; -k|--keep) keep=t ;; + -c|--scissors) + scissors=t ;; + --no-scissors) + scissors=f ;; -r|--resolved) resolved=t ;; --skip) @@ -309,7 +322,7 @@ do --abort) abort=t ;; --rebasing) - rebasing=t threeway=t keep=t ;; + rebasing=t threeway=t keep=t scissors=f ;; -d|--dotest) die "-d option is no longer supported. Do not use." ;; @@ -321,7 +334,7 @@ do git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;; --patch-format) shift ; patch_format="$1" ;; - --reject) + --reject|--ignore-whitespace|--ignore-space-change) git_apply_opt="$git_apply_opt $1" ;; --committer-date-is-author-date) committer_date_is_author_date=t ;; @@ -427,14 +440,14 @@ else split_patches "$@" - # -s, -u, -k, --whitespace, -3, -C, -q and -p flags are kept - # for the resuming session after a patch failure. - # -i can and must be given when resuming. + # -i can and must be given when resuming; everything + # else is kept echo " $git_apply_opt" >"$dotest/apply-opt" echo "$threeway" >"$dotest/threeway" echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" + echo "$scissors" >"$dotest/scissors" echo "$GIT_QUIET" >"$dotest/quiet" echo 1 >"$dotest/next" if test -n "$rebasing" @@ -476,6 +489,12 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi +case "$(cat "$dotest/scissors")" in +t) + scissors=--scissors ;; +f) + scissors=--no-scissors ;; +esac if test "$(cat "$dotest/quiet")" = t then GIT_QUIET=t @@ -530,7 +549,7 @@ do # by the user, or the user can tell us to do so by --resolved flag. case "$resume" in '') - git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \ + git mailinfo $keep $scissors $utf8 "$dotest/msg" "$dotest/patch" \ <"$dotest/$msgnum" >"$dotest/info" || stop_here $this diff --git a/git-compat-util.h b/git-compat-util.h index 9f941e42b..e5e9f39c5 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -57,10 +57,8 @@ # endif #elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && !defined(_M_UNIX) && !defined(sgi) #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ -#ifndef __sun__ #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ #endif -#endif #define _ALL_SOURCE 1 #define _GNU_SOURCE 1 #define _BSD_SOURCE 1 @@ -176,6 +174,8 @@ extern char *gitbasename(char *); #endif #endif +#include "compat/bswap.h" + /* General helper functions */ extern void usage(const char *err) NORETURN; extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2))); diff --git a/git-cvsimport.perl b/git-cvsimport.perl index e43920296..d7411151d 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -238,7 +238,9 @@ sub conn { } my $rr = ":pserver:$user\@$serv:$port$repo"; - unless ($pass) { + if ($pass) { + $pass = $self->_scramble($pass); + } else { open(H,$ENV{'HOME'}."/.cvspass") and do { # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z while (<H>) { @@ -252,7 +254,6 @@ sub conn { } }; } - $pass="A" unless $pass; my ($s, $rep); if ($proxyhost) { @@ -484,6 +485,42 @@ sub _fetchfile { return $res; } +sub _scramble { + my ($self, $pass) = @_; + my $scrambled = "A"; + + return $scrambled unless $pass; + + my $pass_len = length($pass); + my @pass_arr = split("", $pass); + my $i; + + # from cvs/src/scramble.c + my @shifts = ( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87, + 111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105, + 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35, + 125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56, + 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223, + 225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190, + 199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193, + 174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212, + 207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246, + 192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176, + 227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127, + 182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195, + 243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152 + ); + + for ($i = 0; $i < $pass_len; $i++) { + $scrambled .= pack("C", $shifts[ord($pass_arr[$i])]); + } + + return $scrambled; +} package main; diff --git a/git-instaweb.sh b/git-instaweb.sh index 5f5cac75e..d96eddbe5 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -77,11 +77,30 @@ start_httpd () { resolve_full_httpd # don't quote $full_httpd, there can be arguments to it (-f) - $full_httpd "$fqgitdir/gitweb/httpd.conf" - if test $? != 0; then - echo "Could not execute http daemon $httpd." - exit 1 - fi + case "$httpd" in + *mongoose*) + #The mongoose server doesn't have a daemon mode so we'll have to fork it + $full_httpd "$fqgitdir/gitweb/httpd.conf" & + #Save the pid before doing anything else (we'll print it later) + pid=$! + + if test $? != 0; then + echo "Could not execute http daemon $httpd." + exit 1 + fi + + cat > "$fqgitdir/pid" <<EOF +$pid +EOF + ;; + *) + $full_httpd "$fqgitdir/gitweb/httpd.conf" + if test $? != 0; then + echo "Could not execute http daemon $httpd." + exit 1 + fi + ;; + esac } stop_httpd () { @@ -308,6 +327,31 @@ EOF fi } +mongoose_conf() { + cat > "$conf" <<EOF +# Mongoose web server configuration file. +# Lines starting with '#' and empty lines are ignored. +# For detailed description of every option, visit +# http://code.google.com/p/mongoose/wiki/MongooseManual + +root $fqgitdir/gitweb +ports $port +index_files gitweb.cgi +#ssl_cert $fqgitdir/gitweb/ssl_cert.pem +error_log $fqgitdir/gitweb/error.log +access_log $fqgitdir/gitweb/access.log + +#cgi setup +cgi_env PATH=/usr/local/bin:/usr/bin:/bin,GIT_DIR=$GIT_DIR,GIT_EXEC_PATH=$GIT_EXEC_PATH +cgi_interp $PERL +cgi_ext cgi,pl + +# mimetype mapping +mime_types .gz=application/x-gzip,.tar.gz=application/x-tgz,.tgz=application/x-tgz,.tar=application/x-tar,.zip=application/zip,.gif=image/gif,.jpg=image/jpeg,.jpeg=image/jpeg,.png=image/png,.css=text/css,.html=text/html,.htm=text/html,.js=text/javascript,.c=text/plain,.cpp=text/plain,.log=text/plain,.conf=text/plain,.text=text/plain,.txt=text/plain,.dtd=text/xml,.bz2=application/x-bzip,.tbz=application/x-bzip-compressed-tar,.tar.bz2=application/x-bzip-compressed-tar +EOF +} + + script=' s#^(my|our) \$projectroot =.*#$1 \$projectroot = "'$(dirname "$fqgitdir")'";#; s#(my|our) \$gitbin =.*#$1 \$gitbin = "'$GIT_EXEC_PATH'";#; @@ -344,6 +388,9 @@ case "$httpd" in webrick) webrick_conf ;; +*mongoose*) + mongoose_conf + ;; *) echo "Unknown httpd specified: $httpd" exit 1 diff --git a/git-pull.sh b/git-pull.sh index 0f2418297..0bbd5bf7d 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -119,11 +119,19 @@ error_on_no_merge_candidates () { } test true = "$rebase" && { - git update-index --ignore-submodules --refresh && - git diff-files --ignore-submodules --quiet && - git diff-index --ignore-submodules --cached --quiet HEAD -- || - die "refusing to pull with rebase: your working tree is not up-to-date" - + if ! git rev-parse -q --verify HEAD >/dev/null + then + # On an unborn branch + if test -f "$GIT_DIR/index" + then + die "updating an unborn branch with changes added to the index" + fi + else + git update-index --ignore-submodules --refresh && + git diff-files --ignore-submodules --quiet && + git diff-index --ignore-submodules --cached --quiet HEAD -- || + die "refusing to pull with rebase: your working tree is not up-to-date" + fi oldremoteref= && . git-parse-remote && remoteref="$(get_remote_merge_branch "$@" 2>/dev/null)" && diff --git a/git-rebase.sh b/git-rebase.sh index 18bc6946c..6ec155cf0 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -333,6 +333,9 @@ do ;; esac ;; + --ignore-whitespace) + git_am_opt="$git_am_opt $1" + ;; --committer-date-is-author-date|--ignore-date) git_am_opt="$git_am_opt $1" force_rebase=t @@ -382,8 +385,10 @@ else fi # The tree must be really really clean. -if ! git update-index --ignore-submodules --refresh; then - die "cannot rebase: you have unstaged changes" +if ! git update-index --ignore-submodules --refresh > /dev/null; then + echo >&2 "cannot rebase: you have unstaged changes" + git diff-files --name-status -r --ignore-submodules -- >&2 + exit 1 fi diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) case "$diff" in diff --git a/git-request-pull.sh b/git-request-pull.sh index fd95beada..630ceddf0 100755 --- a/git-request-pull.sh +++ b/git-request-pull.sh @@ -8,13 +8,33 @@ USAGE='<start> <url> [<end>]' LONG_USAGE='Summarizes the changes between two commits to the standard output, and includes the given URL in the generated summary.' SUBDIRECTORY_OK='Yes' -OPTIONS_SPEC= +OPTIONS_SPEC='git request-pull [options] start url [end] +-- +p show patch text as well +' + . git-sh-setup . git-parse-remote GIT_PAGER= export GIT_PAGER +patch= +while case "$#" in 0) break ;; esac +do + case "$1" in + -p) + patch=-p ;; + --) + shift; break ;; + -*) + usage ;; + *) + break ;; + esac + shift +done + base=$1 url=$2 head=${3-HEAD} @@ -54,5 +74,5 @@ echo " $url $branch" echo git shortlog ^$baserev $headrev -git diff -M --stat --summary $merge_base $headrev +git diff -M --stat --summary $patch $merge_base..$headrev exit $status diff --git a/git-stash.sh b/git-stash.sh index 03e589f76..4febbbfa5 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -7,7 +7,7 @@ USAGE="list [<options>] or: $dashless drop [-q|--quiet] [<stash>] or: $dashless ( pop | apply ) [--index] [-q|--quiet] [<stash>] or: $dashless branch <branchname> [<stash>] - or: $dashless [save [--keep-index] [-q|--quiet] [<message>]] + or: $dashless [save [-k|--keep-index] [-q|--quiet] [<message>]] or: $dashless clear" SUBDIRECTORY_OK=Yes @@ -21,6 +21,14 @@ trap 'rm -f "$TMP-*"' 0 ref_stash=refs/stash +if git config --get-colorbool color.interactive; then + help_color="$(git config --get-color color.interactive.help 'red bold')" + reset_color="$(git config --get-color '' reset)" +else + help_color= + reset_color= +fi + no_changes () { git diff-index --quiet --cached HEAD --ignore-submodules -- && git diff-files --quiet --ignore-submodules @@ -68,19 +76,44 @@ create_stash () { git commit-tree $i_tree -p $b_commit) || die "Cannot save the current index state" - # state of the working tree - w_tree=$( ( + if test -z "$patch_mode" + then + + # state of the working tree + w_tree=$( ( + rm -f "$TMP-index" && + cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && + GIT_INDEX_FILE="$TMP-index" && + export GIT_INDEX_FILE && + git read-tree -m $i_tree && + git add -u && + git write-tree && + rm -f "$TMP-index" + ) ) || + die "Cannot save the current worktree state" + + else + rm -f "$TMP-index" && - cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" && - GIT_INDEX_FILE="$TMP-index" && - export GIT_INDEX_FILE && - git read-tree -m $i_tree && - git add -u && - git write-tree && - rm -f "$TMP-index" - ) ) || + GIT_INDEX_FILE="$TMP-index" git read-tree HEAD && + + # find out what the user wants + GIT_INDEX_FILE="$TMP-index" \ + git add--interactive --patch=stash -- && + + # state of the working tree + w_tree=$(GIT_INDEX_FILE="$TMP-index" git write-tree) || die "Cannot save the current worktree state" + git diff-tree -p HEAD $w_tree > "$TMP-patch" && + test -s "$TMP-patch" || + die "No changes selected" + + rm -f "$TMP-index" || + die "Cannot remove temporary index (can't happen)" + + fi + # create the stash if test -z "$stash_msg" then @@ -95,15 +128,31 @@ create_stash () { save_stash () { keep_index= + patch_mode= while test $# != 0 do case "$1" in - --keep-index) + -k|--keep-index) + keep_index=t + ;; + --no-keep-index) + keep_index= + ;; + -p|--patch) + patch_mode=t keep_index=t ;; -q|--quiet) GIT_QUIET=t ;; + --) + shift + break + ;; + -*) + echo "error: unknown option for 'stash save': $1" + usage + ;; *) break ;; @@ -131,11 +180,22 @@ save_stash () { die "Cannot save the current status" say Saved working directory and index state "$stash_msg" - git reset --hard ${GIT_QUIET:+-q} - - if test -n "$keep_index" && test -n $i_tree + if test -z "$patch_mode" then - git read-tree --reset -u $i_tree + git reset --hard ${GIT_QUIET:+-q} + + if test -n "$keep_index" && test -n $i_tree + then + git read-tree --reset -u $i_tree + fi + else + git apply -R < "$TMP-patch" || + die "Cannot remove worktree changes" + + if test -z "$keep_index" + then + git reset + fi fi } @@ -162,10 +222,6 @@ show_stash () { } apply_stash () { - git update-index -q --refresh && - git diff-files --quiet --ignore-submodules || - die 'Cannot apply to a dirty working tree, please stage your changes' - unstash_index= while test $# != 0 @@ -184,18 +240,27 @@ apply_stash () { shift done - # current index state - c_tree=$(git write-tree) || - die 'Cannot apply a stash in the middle of a merge' + if test $# = 0 + then + have_stash || die 'Nothing to apply' + fi # stash records the work tree, and is a merge between the # base commit (first parent) and the index tree (second parent). - s=$(git rev-parse --verify --default $ref_stash "$@") && - w_tree=$(git rev-parse --verify "$s:") && - b_tree=$(git rev-parse --verify "$s^1:") && - i_tree=$(git rev-parse --verify "$s^2:") || + s=$(git rev-parse --quiet --verify --default $ref_stash "$@") && + w_tree=$(git rev-parse --quiet --verify "$s:") && + b_tree=$(git rev-parse --quiet --verify "$s^1:") && + i_tree=$(git rev-parse --quiet --verify "$s^2:") || die "$*: no valid stashed state found" + git update-index -q --refresh && + git diff-files --quiet --ignore-submodules || + die 'Cannot apply to a dirty working tree, please stage your changes' + + # current index state + c_tree=$(git write-tree) || + die 'Cannot apply a stash in the middle of a merge' + unstashed_index_tree= if test -n "$unstash_index" && test "$b_tree" != "$i_tree" && test "$c_tree" != "$i_tree" @@ -302,6 +367,18 @@ apply_to_branch () { drop_stash $stash } +# The default command is "save" if nothing but options are given +seen_non_option= +for opt +do + case "$opt" in + -*) ;; + *) seen_non_option=t; break ;; + esac +done + +test -n "$seen_non_option" || set "save" "$@" + # Main command set case "$1" in list) @@ -353,12 +430,13 @@ branch) apply_to_branch "$@" ;; *) - if test $# -eq 0 - then + case $# in + 0) save_stash && say '(To restore them type "git stash apply")' - else + ;; + *) usage - fi + esac ;; esac diff --git a/git-submodule.sh b/git-submodule.sh index ebed711da..bfbd36b6f 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -4,9 +4,14 @@ # # Copyright (c) 2007 Lars Hjemli -USAGE="[--quiet] [--cached] \ -[add [-b branch] <repo> <path>]|[status|init|update [-i|--init] [-N|--no-fetch] [--rebase|--merge]|summary [-n|--summary-limit <n>] [<commit>]] \ -[--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]" +dashless=$(basename "$0" | sed -e 's/-/ /') +USAGE="[--quiet] add [-b branch] [--reference <repository>] [--] <repository> <path> + or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] + or: $dashless [--quiet] init [--] [<path>...] + or: $dashless [--quiet] update [--init] [-N|--no-fetch] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...] + or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] + or: $dashless [--quiet] foreach [--recursive] <command> + or: $dashless [--quiet] sync [--] [<path>...]" OPTIONS_SPEC= . git-sh-setup . git-parse-remote @@ -16,8 +21,10 @@ command= branch= reference= cached= +files= nofetch= update= +prefix= # Resolve relative url by appending to parent's url resolve_relative_url () @@ -237,13 +244,43 @@ cmd_add() # cmd_foreach() { + # parse $args after "submodule ... foreach". + while test $# -ne 0 + do + case "$1" in + -q|--quiet) + GIT_QUIET=1 + ;; + --recursive) + recursive=1 + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + module_list | while read mode sha1 stage path do if test -e "$path"/.git then - say "Entering '$path'" - (cd "$path" && eval "$@") || + say "Entering '$prefix$path'" + name=$(module_name "$path") + ( + prefix="$prefix$path/" + unset GIT_DIR + cd "$path" && + eval "$@" && + if test -n "$recursive" + then + cmd_foreach "--recursive" "$@" + fi + ) || die "Stopping at '$path'; script returned non-zero status." fi done @@ -316,6 +353,7 @@ cmd_init() cmd_update() { # parse $args after "submodule ... update". + orig_args="$@" while test $# -ne 0 do case "$1" in @@ -348,6 +386,10 @@ cmd_update() shift update="merge" ;; + --recursive) + shift + recursive=1 + ;; --) shift break @@ -434,6 +476,12 @@ cmd_update() die "Unable to $action '$sha1' in submodule path '$path'" say "Submodule path '$path': $msg '$sha1'" fi + + if test -n "$recursive" + then + (unset GIT_DIR; cd "$path" && cmd_update $orig_args) || + die "Failed to recurse into submodule path '$path'" + fi done } @@ -460,6 +508,7 @@ set_name_rev () { cmd_summary() { summary_limit=-1 for_status= + diff_cmd=diff-index # parse $args after "submodule ... summary". while test $# -ne 0 @@ -468,6 +517,9 @@ cmd_summary() { --cached) cached="$1" ;; + --files) + files="$1" + ;; --for-status) for_status="$1" ;; @@ -504,9 +556,17 @@ cmd_summary() { head=HEAD fi + if [ -n "$files" ] + then + test -n "$cached" && + die "--cached cannot be used with --files" + diff_cmd=diff-files + head= + fi + cd_to_toplevel # Get modified modules cared by user - modules=$(git diff-index $cached --raw $head -- "$@" | + modules=$(git $diff_cmd $cached --raw $head -- "$@" | egrep '^:([0-7]* )?160000' | while read mod_src mod_dst sha1_src sha1_dst status name do @@ -520,7 +580,7 @@ cmd_summary() { test -z "$modules" && return - git diff-index $cached --raw $head -- $modules | + git $diff_cmd $cached --raw $head -- $modules | egrep '^:([0-7]* )?160000' | cut -c2- | while read mod_src mod_dst sha1_src sha1_dst status name @@ -643,6 +703,7 @@ cmd_summary() { cmd_status() { # parse $args after "submodule ... status". + orig_args="$@" while test $# -ne 0 do case "$1" in @@ -652,6 +713,9 @@ cmd_status() --cached) cached=1 ;; + --recursive) + recursive=1 + ;; --) shift break @@ -671,22 +735,34 @@ cmd_status() do name=$(module_name "$path") || exit url=$(git config submodule."$name".url) + displaypath="$prefix$path" if test -z "$url" || ! test -d "$path"/.git -o -f "$path"/.git then - say "-$sha1 $path" + say "-$sha1 $displaypath" continue; fi set_name_rev "$path" "$sha1" if git diff-files --quiet -- "$path" then - say " $sha1 $path$revname" + say " $sha1 $displaypath$revname" else if test -z "$cached" then sha1=$(unset GIT_DIR; cd "$path" && git rev-parse --verify HEAD) set_name_rev "$path" "$sha1" fi - say "+$sha1 $path$revname" + say "+$sha1 $displaypath$revname" + fi + + if test -n "$recursive" + then + ( + prefix="$displaypath/" + unset GIT_DIR + cd "$path" && + cmd_status $orig_args + ) || + die "Failed to recurse into submodule path '$path'" fi done } diff --git a/git-svn.perl b/git-svn.perl index 24bdbf5b8..e0ec258e3 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,6 +21,15 @@ $Git::SVN::default_ref_id = $ENV{GIT_SVN_ID} || 'git-svn'; $Git::SVN::Ra::_log_window_size = 100; $Git::SVN::_minimize_url = 'unset'; +if (! exists $ENV{SVN_SSH}) { + if (exists $ENV{GIT_SSH}) { + $ENV{SVN_SSH} = $ENV{GIT_SSH}; + if ($^O eq 'msys') { + $ENV{SVN_SSH} =~ s/\\/\\\\/g; + } + } +} + $Git::SVN::Log::TZ = $ENV{TZ}; $ENV{TZ} = 'UTC'; $| = 1; # unbuffer STDOUT @@ -764,6 +773,7 @@ sub cmd_show_ignore { print STDOUT "\n# $path\n"; my $s = $props->{'svn:ignore'} or return; $s =~ s/[\r\n]+/\n/g; + $s =~ s/^\n+//; chomp $s; $s =~ s#^#$path#gm; print STDOUT "$s\n"; @@ -801,6 +811,7 @@ sub cmd_create_ignore { open(GITIGNORE, '>', $ignore) or fatal("Failed to open `$ignore' for writing: $!"); $s =~ s/[\r\n]+/\n/g; + $s =~ s/^\n+//; chomp $s; # Prefix all patterns so that the ignore doesn't apply # to sub-directories. @@ -907,7 +918,7 @@ sub cmd_multi_init { } do_git_init_db(); if (defined $_trunk) { - my $trunk_ref = $_prefix . 'trunk'; + my $trunk_ref = 'refs/remotes/' . $_prefix . 'trunk'; # try both old-style and new-style lookups: my $gs_trunk = eval { Git::SVN->new($trunk_ref) }; unless ($gs_trunk) { @@ -1154,6 +1165,17 @@ sub post_fetch_checkout { my $gs = $Git::SVN::_head or return; return if verify_ref('refs/heads/master^0'); + # look for "trunk" ref if it exists + my $remote = Git::SVN::read_all_remotes()->{$gs->{repo_id}}; + my $fetch = $remote->{fetch}; + if ($fetch) { + foreach my $p (keys %$fetch) { + basename($fetch->{$p}) eq 'trunk' or next; + $gs = Git::SVN->new($fetch->{$p}, $gs->{repo_id}, $p); + last; + } + } + my $valid_head = verify_ref('HEAD^0'); command_noisy(qw(update-ref refs/heads/master), $gs->refname); return if ($valid_head || !verify_ref('HEAD^0')); @@ -1209,6 +1231,7 @@ sub complete_url_ls_init { } command_oneline('config', $k, $gs->{url}) unless $orig_url; my $remote_path = "$gs->{path}/$repo_path"; + $remote_path =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg; $remote_path =~ s#/+#/#g; $remote_path =~ s#^/##g; $remote_path .= "/*" if $remote_path !~ /\*/; @@ -1641,23 +1664,23 @@ sub resolve_local_globs { return unless defined $glob_spec; my $ref = $glob_spec->{ref}; my $path = $glob_spec->{path}; - foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) { - next unless m#^refs/remotes/$ref->{regex}$#; + foreach (command(qw#for-each-ref --format=%(refname) refs/#)) { + next unless m#^$ref->{regex}$#; my $p = $1; my $pathname = desanitize_refname($path->full_path($p)); my $refname = desanitize_refname($ref->full_path($p)); if (my $existing = $fetch->{$pathname}) { if ($existing ne $refname) { die "Refspec conflict:\n", - "existing: refs/remotes/$existing\n", - " globbed: refs/remotes/$refname\n"; + "existing: $existing\n", + " globbed: $refname\n"; } - my $u = (::cmt_metadata("refs/remotes/$refname"))[0]; + my $u = (::cmt_metadata("$refname"))[0]; $u =~ s!^\Q$url\E(/|$)!! or die - "refs/remotes/$refname: '$url' not found in '$u'\n"; + "$refname: '$url' not found in '$u'\n"; if ($pathname ne $u) { warn "W: Refspec glob conflict ", - "(ref: refs/remotes/$refname):\n", + "(ref: $refname):\n", "expected path: $pathname\n", " real path: $u\n", "Continuing ahead with $u\n"; @@ -1735,33 +1758,35 @@ sub read_all_remotes { my $use_svm_props = eval { command_oneline(qw/config --bool svn.useSvmProps/) }; $use_svm_props = $use_svm_props eq 'true' if $use_svm_props; + my $svn_refspec = qr{\s*/?(.*?)\s*:\s*(.+?)\s*}; foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) { - if (m!^(.+)\.fetch=\s*(.*)\s*:\s*(.+)\s*$!) { - my ($remote, $local_ref, $_remote_ref) = ($1, $2, $3); - die("svn-remote.$remote: remote ref '$_remote_ref' " - . "must start with 'refs/remotes/'\n") - unless $_remote_ref =~ m{^refs/remotes/(.+)}; - my $remote_ref = $1; - $local_ref =~ s{^/}{}; + if (m!^(.+)\.fetch=$svn_refspec$!) { + my ($remote, $local_ref, $remote_ref) = ($1, $2, $3); + die("svn-remote.$remote: remote ref '$remote_ref' " + . "must start with 'refs/'\n") + unless $remote_ref =~ m{^refs/}; $r->{$remote}->{fetch}->{$local_ref} = $remote_ref; $r->{$remote}->{svm} = {} if $use_svm_props; } elsif (m!^(.+)\.usesvmprops=\s*(.*)\s*$!) { $r->{$1}->{svm} = {}; } elsif (m!^(.+)\.url=\s*(.*)\s*$!) { $r->{$1}->{url} = $2; - } elsif (m!^(.+)\.(branches|tags)= - (.*):refs/remotes/(.+)\s*$/!x) { - my ($p, $g) = ($3, $4); + } elsif (m!^(.+)\.(branches|tags)=$svn_refspec$!) { + my ($remote, $t, $local_ref, $remote_ref) = + ($1, $2, $3, $4); + die("svn-remote.$remote: remote ref '$remote_ref' ($t) " + . "must start with 'refs/'\n") + unless $remote_ref =~ m{^refs/}; my $rs = { - t => $2, - remote => $1, - path => Git::SVN::GlobSpec->new($p), - ref => Git::SVN::GlobSpec->new($g) }; + t => $t, + remote => $remote, + path => Git::SVN::GlobSpec->new($local_ref), + ref => Git::SVN::GlobSpec->new($remote_ref) }; if (length($rs->{ref}->{right}) != 0) { die "The '*' glob character must be the last ", - "character of '$g'\n"; + "character of '$remote_ref'\n"; } - push @{ $r->{$1}->{$2} }, $rs; + push @{ $r->{$remote}->{$t} }, $rs; } } @@ -1869,14 +1894,15 @@ sub init_remote_config { } } my ($xrepo_id, $xpath) = find_ref($self->refname); - if (defined $xpath) { + if (!$no_write && defined $xpath) { die "svn-remote.$xrepo_id.fetch already set to track ", - "$xpath:refs/remotes/", $self->refname, "\n"; + "$xpath:", $self->refname, "\n"; } unless ($no_write) { command_noisy('config', "svn-remote.$self->{repo_id}.url", $url); $self->{path} =~ s{^/}{}; + $self->{path} =~ s{%([0-9A-F]{2})}{chr hex($1)}ieg; command_noisy('config', '--add', "svn-remote.$self->{repo_id}.fetch", "$self->{path}:".$self->refname); @@ -1946,7 +1972,7 @@ sub find_ref { my ($ref_id) = @_; foreach (command(qw/config -l/)) { next unless m!^svn-remote\.(.+)\.fetch= - \s*(.*)\s*:\s*refs/remotes/(.+)\s*$!x; + \s*/?(.*?)\s*:\s*(.+?)\s*$!x; my ($repo_id, $path, $ref) = ($1, $2, $3); if ($ref eq $ref_id) { $path = '' if ($path =~ m#^\./?#); @@ -1963,16 +1989,16 @@ sub new { if (!defined $repo_id) { die "Could not find a \"svn-remote.*.fetch\" key ", "in the repository configuration matching: ", - "refs/remotes/$ref_id\n"; + "$ref_id\n"; } } my $self = _new($class, $repo_id, $ref_id, $path); if (!defined $self->{path} || !length $self->{path}) { my $fetch = command_oneline('config', '--get', "svn-remote.$repo_id.fetch", - ":refs/remotes/$ref_id\$") or + ":$ref_id\$") or die "Failed to read \"svn-remote.$repo_id.fetch\" ", - "\":refs/remotes/$ref_id\$\" in config\n"; + "\":$ref_id\$\" in config\n"; ($self->{path}, undef) = split(/\s*:\s*/, $fetch); } $self->{url} = command_oneline('config', '--get', @@ -1983,7 +2009,7 @@ sub new { } sub refname { - my ($refname) = "refs/remotes/$_[0]->{ref_id}" ; + my ($refname) = $_[0]->{ref_id} ; # It cannot end with a slash /, we'll throw up on this because # SVN can't have directories with a slash in their name, either: @@ -3263,7 +3289,7 @@ sub _rev_map_get { my $i = int(($l/24 + $u/24) / 2) * 24; sysseek($fh, $i, SEEK_SET) or croak "seek: $!"; sysread($fh, my $buf, 24) == 24 or croak "read: $!"; - my ($r, $c) = unpack('NH40', $buf); + my ($r, $c) = unpack(rev_map_fmt, $buf); if ($r < $rev) { $l = $i + 24; @@ -3318,12 +3344,24 @@ sub _new { $repo_id = $Git::SVN::default_repo_id; } unless (defined $ref_id && length $ref_id) { - $_[2] = $ref_id = $Git::SVN::default_ref_id; + $_prefix = '' unless defined($_prefix); + $_[2] = $ref_id = + "refs/remotes/$_prefix$Git::SVN::default_ref_id"; } $_[1] = $repo_id; my $dir = "$ENV{GIT_DIR}/svn/$ref_id"; + + # Older repos imported by us used $GIT_DIR/svn/foo instead of + # $GIT_DIR/svn/refs/remotes/foo when tracking refs/remotes/foo + if ($ref_id =~ m{^refs/remotes/(.*)}) { + my $old_dir = "$ENV{GIT_DIR}/svn/$1"; + if (-d $old_dir && ! -d $dir) { + $dir = $old_dir; + } + } + $_[3] = $path = '' unless (defined $path); - mkpath(["$ENV{GIT_DIR}/svn"]); + mkpath([$dir]); bless { ref_id => $ref_id, dir => $dir, index => "$dir/index", path => $path, config => "$ENV{GIT_DIR}/svn/config", @@ -5496,7 +5534,7 @@ sub minimize_connections { my $pfx = "svn-remote.$x->{old_repo_id}"; my $old_fetch = quotemeta("$x->{old_path}:". - "refs/remotes/$x->{ref_id}"); + "$x->{ref_id}"); command_noisy(qw/config --unset/, "$pfx.fetch", '^'. $old_fetch . '$'); delete $r->{$x->{old_repo_id}}-> @@ -5565,7 +5603,7 @@ sub new { my ($class, $glob) = @_; my $re = $glob; $re =~ s!/+$!!g; # no need for trailing slashes - $re =~ m!^([^*]*)(\*(?:/\*)*)([^*]*)$!; + $re =~ m!^([^*]*)(\*(?:/\*)*)(.*)$!; my $temp = $re; my ($left, $right) = ($1, $3); $re = $2; diff --git a/git-web--browse.sh b/git-web--browse.sh index 4f5c740df..a578c3a73 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -111,7 +111,8 @@ if test -z "$browser" ; then browser_candidates="w3m links lynx" fi # SECURITYSESSIONID indicates an OS X GUI login session - if test -n "$SECURITYSESSIONID"; then + if test -n "$SECURITYSESSIONID" \ + -o "$TERM_PROGRAM" = "Apple_Terminal" ; then browser_candidates="open $browser_candidates" fi # /bin/start indicates MinGW @@ -5,7 +5,10 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]\n" + " [-p|--paginate|--no-pager]\n" + " [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]\n" + " [--help] COMMAND [ARGS]"; const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; @@ -309,9 +312,6 @@ static void handle_internal_command(int argc, const char **argv) { "get-tar-commit-id", cmd_get_tar_commit_id }, { "grep", cmd_grep, RUN_SETUP | USE_PAGER }, { "help", cmd_help }, -#ifndef NO_CURL - { "http-fetch", cmd_http_fetch, RUN_SETUP }, -#endif { "init", cmd_init_db }, { "init-db", cmd_init_db }, { "log", cmd_log, RUN_SETUP | USE_PAGER }, @@ -339,6 +339,7 @@ static void handle_internal_command(int argc, const char **argv) { "receive-pack", cmd_receive_pack }, { "reflog", cmd_reflog, RUN_SETUP }, { "remote", cmd_remote, RUN_SETUP }, + { "replace", cmd_replace, RUN_SETUP }, { "repo-config", cmd_config }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, @@ -358,6 +359,7 @@ static void handle_internal_command(int argc, const char **argv) { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, { "update-index", cmd_update_index, RUN_SETUP }, { "update-ref", cmd_update_ref, RUN_SETUP }, + { "update-server-info", cmd_update_server_info, RUN_SETUP }, { "upload-archive", cmd_upload_archive }, { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, @@ -416,13 +418,9 @@ static void execv_dashed_external(const char **argv) * if we fail because the command is not found, it is * OK to return. Otherwise, we just pass along the status code. */ - status = run_command_v_opt(argv, 0); - if (status != -ERR_RUN_COMMAND_EXEC) { - if (IS_RUN_COMMAND_ERR(status)) - die("unable to run '%s'", argv[0]); - exit(-status); - } - errno = ENOENT; /* as if we called execvp */ + status = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE); + if (status >= 0 || errno != ENOENT) + exit(status); argv[0] = tmp; diff --git a/git.spec.in b/git.spec.in index 4be0834f0..ab224f7ea 100644 --- a/git.spec.in +++ b/git.spec.in @@ -233,7 +233,7 @@ rm -rf $RPM_BUILD_ROOT * Tue Mar 27 2007 Eygene Ryabinkin <rea-git@codelabs.ru> - Added the git-p4 package: Perforce import stuff. -* Mon Feb 13 2007 Nicolas Pitre <nico@cam.org> +* Mon Feb 13 2007 Nicolas Pitre <nico@fluxnic.net> - Update core package description (Git isn't as stupid as it used to be) * Mon Feb 12 2007 Junio C Hamano <junkio@cox.net> diff --git a/gitk-git/gitk b/gitk-git/gitk index 4604c831f..8c08310e6 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -288,7 +288,7 @@ proc parseviewrevs {view revs} { if {$sdm != 2} { lappend ret $id } else { - lset ret end [lindex $ret end]...$id + lset ret end $id...[lindex $ret end] } lappend pos $id } @@ -1677,6 +1677,7 @@ proc readrefs {} { global tagids idtags headids idheads tagobjid global otherrefids idotherrefs mainhead mainheadid global selecthead selectheadid + global hideremotes foreach v {tagids idtags headids idheads otherrefids idotherrefs} { catch {unset $v} @@ -1689,7 +1690,7 @@ proc readrefs {} { if {![string match "refs/*" $ref]} continue set name [string range $ref 5 end] if {[string match "remotes/*" $name]} { - if {![string match "*/HEAD" $name]} { + if {![string match "*/HEAD" $name] && !$hideremotes} { set headids($name) $id lappend idheads($id) $name } @@ -2520,6 +2521,7 @@ proc savestuff {w} { global cmitmode wrapcomment datetimeformat limitdiffs global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor global autoselect extdifftool perfile_attrs markbgcolor + global hideremotes if {$stuffsaved} return if {![winfo viewable .]} return @@ -2539,6 +2541,7 @@ proc savestuff {w} { puts $f [list set wrapcomment $wrapcomment] puts $f [list set autoselect $autoselect] puts $f [list set showneartags $showneartags] + puts $f [list set hideremotes $hideremotes] puts $f [list set showlocalchanges $showlocalchanges] puts $f [list set datetimeformat $datetimeformat] puts $f [list set limitdiffs $limitdiffs] @@ -7906,6 +7909,11 @@ proc gotocommit {} { } set id [lindex $matches 0] } + } else { + if {[catch {set id [exec git rev-parse --verify $sha1string]}]} { + error_popup [mc "Revision %s is not known" $sha1string] + return + } } } if {[commitinview $id $curview]} { @@ -7915,7 +7923,7 @@ proc gotocommit {} { if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} { set msg [mc "SHA1 id %s is not known" $sha1string] } else { - set msg [mc "Tag/Head %s is not known" $sha1string] + set msg [mc "Revision %s is not in the current view" $sha1string] } error_popup $msg } @@ -10383,6 +10391,7 @@ proc doprefs {} { global oldprefs prefstop showneartags showlocalchanges global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor global tabstop limitdiffs autoselect extdifftool perfile_attrs + global hideremotes set top .gitkprefs set prefstop $top @@ -10391,7 +10400,7 @@ proc doprefs {} { return } foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop perfile_attrs} { + limitdiffs tabstop perfile_attrs hideremotes} { set oldprefs($v) [set $v] } toplevel $top @@ -10423,6 +10432,9 @@ proc doprefs {} { checkbutton $top.ntag -text [mc "Display nearby tags"] \ -font optionfont -variable showneartags grid x $top.ntag -sticky w + checkbutton $top.hideremotes -text [mc "Hide remote refs"] \ + -font optionfont -variable hideremotes + grid x $top.hideremotes -sticky w checkbutton $top.ldiff -text [mc "Limit diffs to listed paths"] \ -font optionfont -variable limitdiffs grid x $top.ldiff -sticky w @@ -10547,7 +10559,7 @@ proc prefscan {} { global oldprefs prefstop foreach v {maxwidth maxgraphpct showneartags showlocalchanges \ - limitdiffs tabstop perfile_attrs} { + limitdiffs tabstop perfile_attrs hideremotes} { global $v set $v $oldprefs($v) } @@ -10561,6 +10573,7 @@ proc prefsok {} { global oldprefs prefstop showneartags showlocalchanges global fontpref mainfont textfont uifont global limitdiffs treediffs perfile_attrs + global hideremotes catch {destroy $prefstop} unset prefstop @@ -10606,6 +10619,9 @@ proc prefsok {} { $limitdiffs != $oldprefs(limitdiffs)} { reselectline } + if {$hideremotes != $oldprefs(hideremotes)} { + rereadrefs + } } proc formatdate {d} { @@ -10901,7 +10917,7 @@ proc gitattr {path attr default} { } else { set r "unspecified" if {![catch {set line [exec git check-attr $attr -- $path]}]} { - regexp "(.*): encoding: (.*)" $line m f r + regexp "(.*): $attr: (.*)" $line m f r } set path_attr_cache($attr,$path) $r } @@ -10929,7 +10945,7 @@ proc cache_gitattr {attr pathlist} { set newlist [lrange $newlist $lim end] if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} { foreach row [split $rlist "\n"] { - if {[regexp "(.*): encoding: (.*)" $row m path value]} { + if {[regexp "(.*): $attr: (.*)" $row m path value]} { if {[string index $path 0] eq "\""} { set path [encoding convertfrom [lindex $path 0]] } @@ -11011,6 +11027,7 @@ set mingaplen 100 set cmitmode "patch" set wrapcomment "none" set showneartags 1 +set hideremotes 0 set maxrefs 20 set maxlinelen 200 set showlocalchanges 1 diff --git a/gitweb/INSTALL b/gitweb/INSTALL index 18c9ce35e..b76a0cfff 100644 --- a/gitweb/INSTALL +++ b/gitweb/INSTALL @@ -123,6 +123,15 @@ GITWEB_CONFIG file: $feature{'snapshot'}{'default'} = ['zip', 'tgz']; $feature{'snapshot'}{'override'} = 1; +If you allow overriding for the snapshot feature, you can specify which +snapshot formats are globally disabled. You can also add any command line +options you want (such as setting the compression level). For instance, +you can disable Zip compressed snapshots and set GZip to run at level 6 by +adding the following lines to your $GITWEB_CONFIG: + + $known_snapshot_formats{'zip'}{'disabled'} = 1; + $known_snapshot_formats{'tgz'}{'compressor'} = ['gzip','-6']; + Gitweb repositories ------------------- diff --git a/gitweb/git-favicon.png b/gitweb/git-favicon.png Binary files differindex de637c060..aae35a70e 100644 --- a/gitweb/git-favicon.png +++ b/gitweb/git-favicon.png diff --git a/gitweb/git-logo.png b/gitweb/git-logo.png Binary files differindex 16ae8d538..f4ede2e94 100644 --- a/gitweb/git-logo.png +++ b/gitweb/git-logo.png diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index d05bc3764..8f68fe309 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -226,22 +226,30 @@ th { text-align: left; } -tr.light:hover { - background-color: #edece6; -} - -tr.dark { - background-color: #f6f6f0; +/* do not change row style on hover for 'blame' view */ +tr.light, +table.blame .light:hover { + background-color: #ffffff; } -tr.dark2 { +tr.dark, +table.blame .dark:hover { background-color: #f6f6f0; } +/* currently both use the same, but it can change */ +tr.light:hover, tr.dark:hover { background-color: #edece6; } +/* boundary commits in 'blame' view */ +/* and commits without "previous" */ +tr.boundary td.sha1, +tr.no-previous td.linenr { + font-weight: bold; +} + td { padding: 2px 5px; font-size: 100%; @@ -262,7 +270,7 @@ td.sha1 { font-family: monospace; } -td.error { +.error { color: red; background-color: yellow; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 2cb832753..24b219310 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -160,7 +160,8 @@ our %known_snapshot_formats = ( # 'suffix' => filename suffix, # 'format' => --format for git-archive, # 'compressor' => [compressor command and arguments] - # (array reference, optional)} + # (array reference, optional) + # 'disabled' => boolean (optional)} # 'tgz' => { 'display' => 'tar.gz', @@ -176,6 +177,14 @@ our %known_snapshot_formats = ( 'format' => 'tar', 'compressor' => ['bzip2']}, + 'txz' => { + 'display' => 'tar.xz', + 'type' => 'application/x-xz', + 'suffix' => '.tar.xz', + 'format' => 'tar', + 'compressor' => ['xz'], + 'disabled' => 1}, + 'zip' => { 'display' => 'zip', 'type' => 'application/x-zip', @@ -188,6 +197,7 @@ our %known_snapshot_formats = ( our %known_snapshot_format_aliases = ( 'gzip' => 'tgz', 'bzip2' => 'tbz2', + 'xz' => 'txz', # backward compatibility: legacy gitweb config support 'x-gzip' => undef, 'gz' => undef, @@ -494,7 +504,8 @@ sub filter_snapshot_fmts { exists $known_snapshot_format_aliases{$_} ? $known_snapshot_format_aliases{$_} : $_} @fmts; @fmts = grep { - exists $known_snapshot_formats{$_} } @fmts; + exists $known_snapshot_formats{$_} && + !$known_snapshot_formats{$_}{'disabled'}} @fmts; } our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; @@ -940,10 +951,13 @@ sub href { if (defined $params{'hash_parent_base'}) { $href .= esc_url($params{'hash_parent_base'}); # skip the file_parent if it's the same as the file_name - delete $params{'file_parent'} if $params{'file_parent'} eq $params{'file_name'}; - if (defined $params{'file_parent'} && $params{'file_parent'} !~ /\.\./) { - $href .= ":/".esc_url($params{'file_parent'}); - delete $params{'file_parent'}; + if (defined $params{'file_parent'}) { + if (defined $params{'file_name'} && $params{'file_parent'} eq $params{'file_name'}) { + delete $params{'file_parent'}; + } elsif ($params{'file_parent'} !~ /\.\./) { + $href .= ":/".esc_url($params{'file_parent'}); + delete $params{'file_parent'}; + } } $href .= ".."; delete $params{'hash_parent'}; @@ -1510,10 +1524,10 @@ sub format_subject_html { $long =~ s/[[:cntrl:]]/?/g; return $cgi->a({-href => $href, -class => "list subject", -title => to_utf8($long)}, - esc_html($short) . $extra); + esc_html($short)) . $extra; } else { return $cgi->a({-href => $href, -class => "list subject"}, - esc_html($long) . $extra); + esc_html($long)) . $extra; } } @@ -4800,7 +4814,7 @@ sub git_blame { git_print_page_path($file_name, $ftype, $hash_base); # page body - my @rev_color = qw(light2 dark2); + my @rev_color = qw(light dark); my $num_colors = scalar(@rev_color); my $current_color = 0; my %metainfo = (); @@ -4818,15 +4832,18 @@ HTML my ($full_rev, $orig_lineno, $lineno, $group_size) = ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); if (!exists $metainfo{$full_rev}) { - $metainfo{$full_rev} = {}; + $metainfo{$full_rev} = { 'nprevious' => 0 }; } my $meta = $metainfo{$full_rev}; my $data; while ($data = <$fd>) { chomp $data; last if ($data =~ s/^\t//); # contents of line - if ($data =~ /^(\S+) (.*)$/) { - $meta->{$1} = $2; + if ($data =~ /^(\S+)(?: (.*))?$/) { + $meta->{$1} = $2 unless exists $meta->{$1}; + } + if ($data =~ /^previous /) { + $meta->{'nprevious'}++; } } my $short_rev = substr($full_rev, 0, 8); @@ -4837,7 +4854,11 @@ HTML if ($group_size) { $current_color = ($current_color + 1) % $num_colors; } - print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n"; + my $tr_class = $rev_color[$current_color]; + $tr_class .= ' boundary' if (exists $meta->{'boundary'}); + $tr_class .= ' no-previous' if ($meta->{'nprevious'} == 0); + $tr_class .= ' multiple-previous' if ($meta->{'nprevious'} > 1); + print "<tr id=\"l$lineno\" class=\"$tr_class\">\n"; if ($group_size) { print "<td class=\"sha1\""; print " title=\"". esc_html($author) . ", $date\""; @@ -4847,22 +4868,31 @@ HTML hash=>$full_rev, file_name=>$file_name)}, esc_html($short_rev)); + if ($group_size >= 2) { + my @author_initials = ($author =~ /\b([[:upper:]])\B/g); + if (@author_initials) { + print "<br />" . + esc_html(join('', @author_initials)); + # or join('.', ...) + } + } print "</td>\n"; } - my $parent_commit; - if (!exists $meta->{'parent'}) { - open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") - or die_error(500, "Open git-rev-parse failed"); - $parent_commit = <$dd>; - close $dd; - chomp($parent_commit); - $meta->{'parent'} = $parent_commit; - } else { - $parent_commit = $meta->{'parent'}; - } + # 'previous' <sha1 of parent commit> <filename at commit> + if (exists $meta->{'previous'} && + $meta->{'previous'} =~ /^([a-fA-F0-9]{40}) (.*)$/) { + $meta->{'parent'} = $1; + $meta->{'file_parent'} = unquote($2); + } + my $linenr_commit = + exists($meta->{'parent'}) ? + $meta->{'parent'} : $full_rev; + my $linenr_filename = + exists($meta->{'file_parent'}) ? + $meta->{'file_parent'} : unquote($meta->{'filename'}); my $blamed = href(action => 'blame', - file_name => $meta->{'filename'}, - hash_base => $parent_commit); + file_name => $linenr_filename, + hash_base => $linenr_commit); print "<td class=\"linenr\">"; print $cgi->a({ -href => "$blamed#l$orig_lineno", -class => "linenr" }, @@ -5160,6 +5190,8 @@ sub git_snapshot { die_error(400, "Invalid snapshot format parameter"); } elsif (!exists($known_snapshot_formats{$format})) { die_error(400, "Unknown snapshot format"); + } elsif ($known_snapshot_formats{$format}{'disabled'}) { + die_error(403, "Snapshot format not allowed"); } elsif (!grep($_ eq $format, @snapshot_fmts)) { die_error(403, "Unsupported snapshot format"); } @@ -225,7 +225,12 @@ struct git_graph *graph_init(struct rev_info *opt) graph->num_columns = 0; graph->num_new_columns = 0; graph->mapping_size = 0; - graph->default_column_color = 0; + /* + * Start the column color at the maximum value, since we'll + * always increment it for the first commit we output. + * This way we start at 0 for the first commit. + */ + graph->default_column_color = COLUMN_COLORS_MAX - 1; /* * Allocate a reasonably large default number of columns @@ -286,9 +291,10 @@ static int graph_is_interesting(struct git_graph *graph, struct commit *commit) } /* - * Uninteresting and pruned commits won't be printed + * Otherwise, use get_commit_action() to see if this commit is + * interesting */ - return (commit->object.flags & (UNINTERESTING | TREESAME)) ? 0 : 1; + return get_commit_action(graph->revs, commit) == commit_show; } static struct commit_list *next_interesting_parent(struct git_graph *graph, @@ -499,11 +505,14 @@ static void graph_update_columns(struct git_graph *graph) parent; parent = next_interesting_parent(graph, parent)) { /* - * If this is a merge increment the current + * If this is a merge, or the start of a new + * childless column, increment the current * color. */ - if (graph->num_parents > 1) + if (graph->num_parents > 1 || + !is_commit_in_columns) { graph_increment_column_color(graph); + } graph_insert_into_new_columns(graph, parent->item, &mapping_idx); @@ -80,6 +80,7 @@ struct grep_opt { int pathname; int null_following_name; int color; + int max_depth; int funcname; char color_match[COLOR_MAXLEN]; const char *color_external; diff --git a/builtin-http-fetch.c b/http-fetch.c index f3e63d720..e8f44babd 100644 --- a/builtin-http-fetch.c +++ b/http-fetch.c @@ -1,8 +1,9 @@ #include "cache.h" #include "walker.h" -int cmd_http_fetch(int argc, const char **argv, const char *prefix) +int main(int argc, const char **argv) { + const char *prefix; struct walker *walker; int commits_on_stdin = 0; int commits; @@ -18,6 +19,8 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix) int get_verbosely = 0; int get_recover = 0; + prefix = setup_git_directory(); + git_config(git_default_config, NULL); while (arg < argc && argv[arg][0] == '-') { @@ -995,7 +995,6 @@ int finish_http_pack_request(struct http_pack_request *preq) struct http_pack_request *new_http_pack_request( struct packed_git *target, const char *base_url) { - char *url; char *filename; long prev_posn = 0; char range[RANGE_HEADER_SIZE]; @@ -1009,8 +1008,7 @@ struct http_pack_request *new_http_pack_request( end_url_with_slash(&buf, base_url); strbuf_addf(&buf, "objects/pack/pack-%s.pack", sha1_to_hex(target->sha1)); - url = strbuf_detach(&buf, NULL); - preq->url = xstrdup(url); + preq->url = strbuf_detach(&buf, NULL); filename = sha1_pack_name(target->sha1); snprintf(preq->filename, sizeof(preq->filename), "%s", filename); @@ -1026,7 +1024,7 @@ struct http_pack_request *new_http_pack_request( preq->slot->local = preq->packfile; curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile); curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(preq->slot->curl, CURLOPT_URL, url); + curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url); curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); @@ -1050,6 +1048,8 @@ struct http_pack_request *new_http_pack_request( abort: free(filename); + free(preq->url); + free(preq); return NULL; } @@ -1089,7 +1089,6 @@ struct http_object_request *new_http_object_request(const char *base_url, char *hex = sha1_to_hex(sha1); char *filename; char prevfile[PATH_MAX]; - char *url; int prevlocal; unsigned char prev_buf[PREV_BUF_SIZE]; ssize_t prev_read = 0; @@ -1143,8 +1142,7 @@ struct http_object_request *new_http_object_request(const char *base_url, git_SHA1_Init(&freq->c); - url = get_remote_object_url(base_url, hex, 0); - freq->url = xstrdup(url); + freq->url = get_remote_object_url(base_url, hex, 0); /* * If a previous temp file is present, process what was already @@ -1180,7 +1178,11 @@ struct http_object_request *new_http_object_request(const char *base_url, if (prev_posn>0) { prev_posn = 0; lseek(freq->localfile, 0, SEEK_SET); - ftruncate(freq->localfile, 0); + if (ftruncate(freq->localfile, 0) < 0) { + error("Couldn't truncate temporary file %s for %s: %s", + freq->tmpfile, freq->filename, strerror(errno)); + goto abort; + } } } @@ -1189,7 +1191,7 @@ struct http_object_request *new_http_object_request(const char *base_url, curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq); curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr); - curl_easy_setopt(freq->slot->curl, CURLOPT_URL, url); + curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url); curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); /* @@ -1209,9 +1211,9 @@ struct http_object_request *new_http_object_request(const char *base_url, return freq; - free(url); abort: free(filename); + free(freq->url); free(freq); return NULL; } diff --git a/ll-merge.c b/ll-merge.c index 0571564dd..2d6b6d6cb 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -192,10 +192,6 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, args[2] = cmd.buf; status = run_command_v_opt(args, 0); - if (status < -ERR_RUN_COMMAND_FORK) - ; /* failure in run-command */ - else - status = -status; fd = open(temp[1], O_RDONLY); if (fd < 0) goto bad; diff --git a/merge-recursive.c b/merge-recursive.c index 10d7913b0..f55b7ebe1 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -170,6 +170,18 @@ static int git_merge_trees(int index_only, int rc; struct tree_desc t[3]; struct unpack_trees_options opts; + static const struct unpack_trees_error_msgs msgs = { + /* would_overwrite */ + "Your local changes to '%s' would be overwritten by merge. Aborting.", + /* not_uptodate_file */ + "Your local changes to '%s' would be overwritten by merge. Aborting.", + /* not_uptodate_dir */ + "Updating '%s' would lose untracked files in it. Aborting.", + /* would_lose_untracked */ + "Untracked working tree file '%s' would be %s by merge. Aborting", + /* bind_overlap -- will not happen here */ + NULL, + }; memset(&opts, 0, sizeof(opts)); if (index_only) @@ -181,6 +193,7 @@ static int git_merge_trees(int index_only, opts.fn = threeway_merge; opts.src_index = &the_index; opts.dst_index = &the_index; + opts.msgs = msgs; init_tree_desc_from_tree(t+0, common); init_tree_desc_from_tree(t+1, head); @@ -1188,10 +1201,14 @@ int merge_trees(struct merge_options *o, code = git_merge_trees(o->call_depth, common, head, merge); - if (code != 0) - die("merging of trees %s and %s failed", - sha1_to_hex(head->object.sha1), - sha1_to_hex(merge->object.sha1)); + if (code != 0) { + if (show(o, 4) || o->call_depth) + die("merging of trees %s and %s failed", + sha1_to_hex(head->object.sha1), + sha1_to_hex(merge->object.sha1)); + else + exit(128); + } if (unmerged_cache()) { struct string_list *entries, *re_head, *re_merge; @@ -19,16 +19,17 @@ /* * We refuse to tag something we can't verify. Just because. */ -static int verify_object(unsigned char *sha1, const char *expected_type) +static int verify_object(const unsigned char *sha1, const char *expected_type) { int ret = -1; enum object_type type; unsigned long size; - void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl; + void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); if (buffer) { if (type == type_from_string(expected_type)) - ret = check_sha1_signature(sha1, buffer, size, expected_type); + ret = check_sha1_signature(repl, buffer, size, expected_type); free(buffer); } return ret; diff --git a/mozilla-sha1/sha1.c b/mozilla-sha1/sha1.c deleted file mode 100644 index 95a4ebf49..000000000 --- a/mozilla-sha1/sha1.c +++ /dev/null @@ -1,151 +0,0 @@ -/* - * The contents of this file are subject to the Mozilla Public - * License Version 1.1 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * The Original Code is SHA 180-1 Reference Implementation (Compact version) - * - * The Initial Developer of the Original Code is Paul Kocher of - * Cryptography Research. Portions created by Paul Kocher are - * Copyright (C) 1995-9 by Cryptography Research, Inc. All - * Rights Reserved. - * - * Contributor(s): - * - * Paul Kocher - * - * Alternatively, the contents of this file may be used under the - * terms of the GNU General Public License Version 2 or later (the - * "GPL"), in which case the provisions of the GPL are applicable - * instead of those above. If you wish to allow use of your - * version of this file only under the terms of the GPL and not to - * allow others to use your version of this file under the MPL, - * indicate your decision by deleting the provisions above and - * replace them with the notice and other provisions required by - * the GPL. If you do not delete the provisions above, a recipient - * may use your version of this file under either the MPL or the - * GPL. - */ - -#include "sha1.h" - -static void shaHashBlock(moz_SHA_CTX *ctx); - -void moz_SHA1_Init(moz_SHA_CTX *ctx) { - int i; - - ctx->lenW = 0; - ctx->sizeHi = ctx->sizeLo = 0; - - /* Initialize H with the magic constants (see FIPS180 for constants) - */ - ctx->H[0] = 0x67452301; - ctx->H[1] = 0xefcdab89; - ctx->H[2] = 0x98badcfe; - ctx->H[3] = 0x10325476; - ctx->H[4] = 0xc3d2e1f0; - - for (i = 0; i < 80; i++) - ctx->W[i] = 0; -} - - -void moz_SHA1_Update(moz_SHA_CTX *ctx, const void *_dataIn, int len) { - const unsigned char *dataIn = _dataIn; - int i; - - /* Read the data into W and process blocks as they get full - */ - for (i = 0; i < len; i++) { - ctx->W[ctx->lenW / 4] <<= 8; - ctx->W[ctx->lenW / 4] |= (unsigned int)dataIn[i]; - if ((++ctx->lenW) % 64 == 0) { - shaHashBlock(ctx); - ctx->lenW = 0; - } - ctx->sizeLo += 8; - ctx->sizeHi += (ctx->sizeLo < 8); - } -} - - -void moz_SHA1_Final(unsigned char hashout[20], moz_SHA_CTX *ctx) { - unsigned char pad0x80 = 0x80; - unsigned char pad0x00 = 0x00; - unsigned char padlen[8]; - int i; - - /* Pad with a binary 1 (e.g. 0x80), then zeroes, then length - */ - padlen[0] = (unsigned char)((ctx->sizeHi >> 24) & 255); - padlen[1] = (unsigned char)((ctx->sizeHi >> 16) & 255); - padlen[2] = (unsigned char)((ctx->sizeHi >> 8) & 255); - padlen[3] = (unsigned char)((ctx->sizeHi >> 0) & 255); - padlen[4] = (unsigned char)((ctx->sizeLo >> 24) & 255); - padlen[5] = (unsigned char)((ctx->sizeLo >> 16) & 255); - padlen[6] = (unsigned char)((ctx->sizeLo >> 8) & 255); - padlen[7] = (unsigned char)((ctx->sizeLo >> 0) & 255); - moz_SHA1_Update(ctx, &pad0x80, 1); - while (ctx->lenW != 56) - moz_SHA1_Update(ctx, &pad0x00, 1); - moz_SHA1_Update(ctx, padlen, 8); - - /* Output hash - */ - for (i = 0; i < 20; i++) { - hashout[i] = (unsigned char)(ctx->H[i / 4] >> 24); - ctx->H[i / 4] <<= 8; - } - - /* - * Re-initialize the context (also zeroizes contents) - */ - moz_SHA1_Init(ctx); -} - - -#define SHA_ROT(X,n) (((X) << (n)) | ((X) >> (32-(n)))) - -static void shaHashBlock(moz_SHA_CTX *ctx) { - int t; - unsigned int A,B,C,D,E,TEMP; - - for (t = 16; t <= 79; t++) - ctx->W[t] = - SHA_ROT(ctx->W[t-3] ^ ctx->W[t-8] ^ ctx->W[t-14] ^ ctx->W[t-16], 1); - - A = ctx->H[0]; - B = ctx->H[1]; - C = ctx->H[2]; - D = ctx->H[3]; - E = ctx->H[4]; - - for (t = 0; t <= 19; t++) { - TEMP = SHA_ROT(A,5) + (((C^D)&B)^D) + E + ctx->W[t] + 0x5a827999; - E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP; - } - for (t = 20; t <= 39; t++) { - TEMP = SHA_ROT(A,5) + (B^C^D) + E + ctx->W[t] + 0x6ed9eba1; - E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP; - } - for (t = 40; t <= 59; t++) { - TEMP = SHA_ROT(A,5) + ((B&C)|(D&(B|C))) + E + ctx->W[t] + 0x8f1bbcdc; - E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP; - } - for (t = 60; t <= 79; t++) { - TEMP = SHA_ROT(A,5) + (B^C^D) + E + ctx->W[t] + 0xca62c1d6; - E = D; D = C; C = SHA_ROT(B, 30); B = A; A = TEMP; - } - - ctx->H[0] += A; - ctx->H[1] += B; - ctx->H[2] += C; - ctx->H[3] += D; - ctx->H[4] += E; -} diff --git a/mozilla-sha1/sha1.h b/mozilla-sha1/sha1.h deleted file mode 100644 index aa48a46cf..000000000 --- a/mozilla-sha1/sha1.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * The contents of this file are subject to the Mozilla Public - * License Version 1.1 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of - * the License at http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * The Original Code is SHA 180-1 Header File - * - * The Initial Developer of the Original Code is Paul Kocher of - * Cryptography Research. Portions created by Paul Kocher are - * Copyright (C) 1995-9 by Cryptography Research, Inc. All - * Rights Reserved. - * - * Contributor(s): - * - * Paul Kocher - * - * Alternatively, the contents of this file may be used under the - * terms of the GNU General Public License Version 2 or later (the - * "GPL"), in which case the provisions of the GPL are applicable - * instead of those above. If you wish to allow use of your - * version of this file only under the terms of the GPL and not to - * allow others to use your version of this file under the MPL, - * indicate your decision by deleting the provisions above and - * replace them with the notice and other provisions required by - * the GPL. If you do not delete the provisions above, a recipient - * may use your version of this file under either the MPL or the - * GPL. - */ - -typedef struct { - unsigned int H[5]; - unsigned int W[80]; - int lenW; - unsigned int sizeHi,sizeLo; -} moz_SHA_CTX; - -void moz_SHA1_Init(moz_SHA_CTX *ctx); -void moz_SHA1_Update(moz_SHA_CTX *ctx, const void *dataIn, int len); -void moz_SHA1_Final(unsigned char hashout[20], moz_SHA_CTX *ctx); - -#define git_SHA_CTX moz_SHA_CTX -#define git_SHA1_Init moz_SHA1_Init -#define git_SHA1_Update moz_SHA1_Update -#define git_SHA1_Final moz_SHA1_Final @@ -188,17 +188,18 @@ struct object *parse_object(const unsigned char *sha1) unsigned long size; enum object_type type; int eaten; - void *buffer = read_sha1_file(sha1, &type, &size); + const unsigned char *repl; + void *buffer = read_sha1_file_repl(sha1, &type, &size, &repl); if (buffer) { struct object *obj; - if (check_sha1_signature(sha1, buffer, size, typename(type)) < 0) { + if (check_sha1_signature(repl, buffer, size, typename(type)) < 0) { free(buffer); - error("sha1 mismatch %s\n", sha1_to_hex(sha1)); + error("sha1 mismatch %s\n", sha1_to_hex(repl)); return NULL; } - obj = parse_object_buffer(sha1, type, size, buffer, &eaten); + obj = parse_object_buffer(repl, type, size, buffer, &eaten); if (!eaten) free(buffer); return obj; diff --git a/pack-redundant.c b/pack-redundant.c index 48a12bc13..69a7ab2e2 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -55,16 +55,15 @@ static inline struct llist_item *llist_item_get(void) } else { int i = 1; new = xmalloc(sizeof(struct llist_item) * BLKSIZE); - for(;i < BLKSIZE; i++) { + for (; i < BLKSIZE; i++) llist_item_put(&new[i]); - } } return new; } static void llist_free(struct llist *list) { - while((list->back = list->front)) { + while ((list->back = list->front)) { list->front = list->front->next; llist_item_put(list->back); } @@ -146,7 +145,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, if (cmp > 0) { /* we insert before this entry */ return llist_insert(list, prev, sha1); } - if(!cmp) { /* already exists */ + if (!cmp) { /* already exists */ return l; } prev = l; @@ -168,7 +167,7 @@ redo_from_start: int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) /* not in list, since sorted */ return prev; - if(!cmp) { /* found */ + if (!cmp) { /* found */ if (prev == NULL) { if (hint != NULL && hint != list->front) { /* we don't know the previous element */ @@ -218,7 +217,7 @@ static inline struct pack_list * pack_list_insert(struct pack_list **pl, static inline size_t pack_list_size(struct pack_list *pl) { size_t ret = 0; - while(pl) { + while (pl) { ret++; pl = pl->next; } @@ -396,7 +395,7 @@ static size_t get_pack_redundancy(struct pack_list *pl) return 0; while ((subset = pl->next)) { - while(subset) { + while (subset) { ret += sizeof_union(pl->pack, subset->pack); subset = subset->next; } @@ -427,7 +426,7 @@ static void minimize(struct pack_list **min) pl = local_packs; while (pl) { - if(pl->unique_objects->size) + if (pl->unique_objects->size) pack_list_insert(&unique, pl); else pack_list_insert(&non_unique, pl); @@ -479,7 +478,7 @@ static void minimize(struct pack_list **min) *min = min_perm; /* add the unique packs to the list */ pl = unique; - while(pl) { + while (pl) { pack_list_insert(min, pl); pl = pl->next; } @@ -516,7 +515,7 @@ static void cmp_local_packs(void) struct pack_list *subset, *pl = local_packs; while ((subset = pl)) { - while((subset = subset->next)) + while ((subset = subset->next)) cmp_two_packs(pl, subset); pl = pl->next; } @@ -608,23 +607,23 @@ int main(int argc, char **argv) for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if(!strcmp(arg, "--")) { + if (!strcmp(arg, "--")) { i++; break; } - if(!strcmp(arg, "--all")) { + if (!strcmp(arg, "--all")) { load_all_packs = 1; continue; } - if(!strcmp(arg, "--verbose")) { + if (!strcmp(arg, "--verbose")) { verbose = 1; continue; } - if(!strcmp(arg, "--alt-odb")) { + if (!strcmp(arg, "--alt-odb")) { alt_odb = 1; continue; } - if(*arg == '-') + if (*arg == '-') usage(pack_redundant_usage); else break; @@ -21,8 +21,6 @@ static void pager_preexec(void) FD_ZERO(&in); FD_SET(0, &in); select(1, &in, NULL, &in, NULL); - - setenv("LESS", "FRSX", 0); } #endif @@ -70,6 +68,10 @@ void setup_pager(void) pager_argv[2] = pager; pager_process.argv = pager_argv; pager_process.in = -1; + if (!getenv("LESS")) { + static const char *env[] = { "LESS=FRSX", NULL }; + pager_process.env = env; + } #ifndef __MINGW32__ pager_process.preexec_cb = pager_preexec; #endif diff --git a/parse-options.c b/parse-options.c index f7ce523a6..a64a4d6ee 100644 --- a/parse-options.c +++ b/parse-options.c @@ -511,7 +511,7 @@ static int usage_with_options_internal(const char * const *usagestr, continue; pos = fprintf(stderr, " "); - if (opts->short_name) { + if (opts->short_name && !(opts->flags & PARSE_OPT_NEGHELP)) { if (opts->flags & PARSE_OPT_NODASH) pos += fprintf(stderr, "%c", opts->short_name); else @@ -520,7 +520,9 @@ static int usage_with_options_internal(const char * const *usagestr, if (opts->long_name && opts->short_name) pos += fprintf(stderr, ", "); if (opts->long_name) - pos += fprintf(stderr, "--%s", opts->long_name); + pos += fprintf(stderr, "--%s%s", + (opts->flags & PARSE_OPT_NEGHELP) ? "no-" : "", + opts->long_name); if (opts->type == OPTION_NUMBER) pos += fprintf(stderr, "-NUM"); @@ -547,6 +549,14 @@ void usage_with_options(const char * const *usagestr, exit(129); } +void usage_msg_opt(const char *msg, + const char * const *usagestr, + const struct option *options) +{ + fprintf(stderr, "%s\n\n", msg); + usage_with_options(usagestr, options); +} + int parse_options_usage(const char * const *usagestr, const struct option *opts) { diff --git a/parse-options.h b/parse-options.h index aba30671d..f295a2cf8 100644 --- a/parse-options.h +++ b/parse-options.h @@ -36,6 +36,7 @@ enum parse_opt_option_flags { PARSE_OPT_LASTARG_DEFAULT = 16, PARSE_OPT_NODASH = 32, PARSE_OPT_LITERAL_ARGHELP = 64, + PARSE_OPT_NEGHELP = 128, }; struct option; @@ -80,6 +81,9 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets * (i.e. '<argh>') in the help message. * Useful for options with multiple parameters. + * PARSE_OPT_NEGHELP: says that the long option should always be shown with + * the --no prefix in the usage message. Sometimes + * useful for users of OPTION_NEGBIT. * * `callback`:: * pointer to the callback to use for OPTION_CALLBACK. @@ -141,6 +145,10 @@ extern int parse_options(int argc, const char **argv, const char *prefix, extern NORETURN void usage_with_options(const char * const *usagestr, const struct option *options); +extern NORETURN void usage_msg_opt(const char *msg, + const char * const *usagestr, + const struct option *options); + /*----- incremental advanced APIs -----*/ enum { diff --git a/patch-delta.c b/patch-delta.c index ef748ce96..e02e13bd4 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -2,7 +2,7 @@ * patch-delta.c: * recreate a buffer from a source and the delta produced by diff-delta.c * - * (C) 2005 Nicolas Pitre <nico@cam.org> + * (C) 2005 Nicolas Pitre <nico@fluxnic.net> * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/progress.c b/progress.c index 621c34edc..132ed95a3 100644 --- a/progress.c +++ b/progress.c @@ -1,7 +1,7 @@ /* * Simple text-based progress display module for GIT * - * Copyright (c) 2007 by Nicolas Pitre <nico@cam.org> + * Copyright (c) 2007 by Nicolas Pitre <nico@fluxnic.net> * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/read-cache.c b/read-cache.c index 4e3e272ee..1bbaf1cff 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1065,7 +1065,18 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate, return updated; } -int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, char *seen) +static void show_file(const char * fmt, const char * name, int in_porcelain, + int * first, char *header_msg) +{ + if (in_porcelain && *first && header_msg) { + printf("%s\n", header_msg); + *first=0; + } + printf(fmt, name); +} + +int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, + char *seen, char *header_msg) { int i; int has_errors = 0; @@ -1074,11 +1085,14 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p int quiet = (flags & REFRESH_QUIET) != 0; int not_new = (flags & REFRESH_IGNORE_MISSING) != 0; int ignore_submodules = (flags & REFRESH_IGNORE_SUBMODULES) != 0; + int first = 1; + int in_porcelain = (flags & REFRESH_IN_PORCELAIN); unsigned int options = really ? CE_MATCH_IGNORE_VALID : 0; - const char *needs_update_message; + const char *needs_update_fmt; + const char *needs_merge_fmt; - needs_update_message = ((flags & REFRESH_SAY_CHANGED) - ? "locally modified" : "needs update"); + needs_update_fmt = (in_porcelain ? "M\t%s\n" : "%s: needs update\n"); + needs_merge_fmt = (in_porcelain ? "U\t%s\n" : "%s: needs merge\n"); for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce, *new; int cache_errno = 0; @@ -1094,7 +1108,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p i--; if (allow_unmerged) continue; - printf("%s: needs merge\n", ce->name); + show_file(needs_merge_fmt, ce->name, in_porcelain, &first, header_msg); has_errors = 1; continue; } @@ -1117,7 +1131,7 @@ int refresh_index(struct index_state *istate, unsigned int flags, const char **p } if (quiet) continue; - printf("%s: %s\n", ce->name, needs_update_message); + show_file(needs_update_fmt, ce->name, in_porcelain, &first, header_msg); has_errors = 1; continue; } @@ -668,6 +668,11 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data) return for_each_ref_in("refs/remotes/", fn, cb_data); } +int for_each_replace_ref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref("refs/replace/", fn, 13, 0, cb_data); +} + int for_each_rawref(each_ref_fn fn, void *cb_data) { return do_for_each_ref("refs/", fn, 0, @@ -24,6 +24,7 @@ extern int for_each_ref_in(const char *, each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +extern int for_each_replace_ref(each_ref_fn, void *); /* can be used to learn about broken ref and symref */ extern int for_each_rawref(each_ref_fn, void *); diff --git a/remote-curl.c b/remote-curl.c new file mode 100644 index 000000000..ad6a1637b --- /dev/null +++ b/remote-curl.c @@ -0,0 +1,139 @@ +#include "cache.h" +#include "remote.h" +#include "strbuf.h" +#include "walker.h" +#include "http.h" + +static struct ref *get_refs(struct walker *walker, const char *url) +{ + struct strbuf buffer = STRBUF_INIT; + char *data, *start, *mid; + char *ref_name; + char *refs_url; + int i = 0; + int http_ret; + + struct ref *refs = NULL; + struct ref *ref = NULL; + struct ref *last_ref = NULL; + + refs_url = xmalloc(strlen(url) + 11); + sprintf(refs_url, "%s/info/refs", url); + + http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); + switch (http_ret) { + case HTTP_OK: + break; + case HTTP_MISSING_TARGET: + die("%s not found: did you run git update-server-info on the" + " server?", refs_url); + default: + http_error(refs_url, http_ret); + die("HTTP request failed"); + } + + data = buffer.buf; + start = NULL; + mid = data; + while (i < buffer.len) { + if (!start) { + start = &data[i]; + } + if (data[i] == '\t') + mid = &data[i]; + if (data[i] == '\n') { + data[i] = 0; + ref_name = mid + 1; + ref = xmalloc(sizeof(struct ref) + + strlen(ref_name) + 1); + memset(ref, 0, sizeof(struct ref)); + strcpy(ref->name, ref_name); + get_sha1_hex(start, ref->old_sha1); + if (!refs) + refs = ref; + if (last_ref) + last_ref->next = ref; + last_ref = ref; + start = NULL; + } + i++; + } + + strbuf_release(&buffer); + + ref = alloc_ref("HEAD"); + if (!walker->fetch_ref(walker, ref) && + !resolve_remote_symref(ref, refs)) { + ref->next = refs; + refs = ref; + } else { + free(ref); + } + + strbuf_release(&buffer); + free(refs_url); + return refs; +} + +int main(int argc, const char **argv) +{ + struct remote *remote; + struct strbuf buf = STRBUF_INIT; + const char *url; + struct walker *walker = NULL; + + setup_git_directory(); + if (argc < 2) { + fprintf(stderr, "Remote needed\n"); + return 1; + } + + remote = remote_get(argv[1]); + + if (argc > 2) { + url = argv[2]; + } else { + url = remote->url[0]; + } + + do { + if (strbuf_getline(&buf, stdin, '\n') == EOF) + break; + if (!prefixcmp(buf.buf, "fetch ")) { + char *obj = buf.buf + strlen("fetch "); + if (!walker) + walker = get_http_walker(url, remote); + walker->get_all = 1; + walker->get_tree = 1; + walker->get_history = 1; + walker->get_verbosely = 0; + walker->get_recover = 0; + if (walker_fetch(walker, 1, &obj, NULL, NULL)) + die("Fetch failed."); + printf("\n"); + fflush(stdout); + } else if (!strcmp(buf.buf, "list")) { + struct ref *refs; + struct ref *posn; + if (!walker) + walker = get_http_walker(url, remote); + refs = get_refs(walker, url); + for (posn = refs; posn; posn = posn->next) { + if (posn->symref) + printf("@%s %s\n", posn->symref, posn->name); + else + printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name); + } + printf("\n"); + fflush(stdout); + } else if (!strcmp(buf.buf, "capabilities")) { + printf("fetch\n"); + printf("\n"); + fflush(stdout); + } else { + return 1; + } + strbuf_reset(&buf); + } while (1); + return 0; +} @@ -28,6 +28,11 @@ struct rewrite { int instead_of_nr; int instead_of_alloc; }; +struct rewrites { + struct rewrite **rewrite; + int rewrite_alloc; + int rewrite_nr; +}; static struct remote **remotes; static int remotes_alloc; @@ -41,14 +46,13 @@ static struct branch *current_branch; static const char *default_remote_name; static int explicit_default_remote_name; -static struct rewrite **rewrite; -static int rewrite_alloc; -static int rewrite_nr; +static struct rewrites rewrites; +static struct rewrites rewrites_push; #define BUF_SIZE (2048) static char buffer[BUF_SIZE]; -static const char *alias_url(const char *url) +static const char *alias_url(const char *url, struct rewrites *r) { int i, j; char *ret; @@ -57,14 +61,14 @@ static const char *alias_url(const char *url) longest = NULL; longest_i = -1; - for (i = 0; i < rewrite_nr; i++) { - if (!rewrite[i]) + for (i = 0; i < r->rewrite_nr; i++) { + if (!r->rewrite[i]) continue; - for (j = 0; j < rewrite[i]->instead_of_nr; j++) { - if (!prefixcmp(url, rewrite[i]->instead_of[j].s) && + for (j = 0; j < r->rewrite[i]->instead_of_nr; j++) { + if (!prefixcmp(url, r->rewrite[i]->instead_of[j].s) && (!longest || - longest->len < rewrite[i]->instead_of[j].len)) { - longest = &(rewrite[i]->instead_of[j]); + longest->len < r->rewrite[i]->instead_of[j].len)) { + longest = &(r->rewrite[i]->instead_of[j]); longest_i = i; } } @@ -72,10 +76,10 @@ static const char *alias_url(const char *url) if (!longest) return url; - ret = xmalloc(rewrite[longest_i]->baselen + + ret = xmalloc(r->rewrite[longest_i]->baselen + (strlen(url) - longest->len) + 1); - strcpy(ret, rewrite[longest_i]->base); - strcpy(ret + rewrite[longest_i]->baselen, url + longest->len); + strcpy(ret, r->rewrite[longest_i]->base); + strcpy(ret + r->rewrite[longest_i]->baselen, url + longest->len); return ret; } @@ -101,17 +105,25 @@ static void add_url(struct remote *remote, const char *url) remote->url[remote->url_nr++] = url; } -static void add_url_alias(struct remote *remote, const char *url) -{ - add_url(remote, alias_url(url)); -} - static void add_pushurl(struct remote *remote, const char *pushurl) { ALLOC_GROW(remote->pushurl, remote->pushurl_nr + 1, remote->pushurl_alloc); remote->pushurl[remote->pushurl_nr++] = pushurl; } +static void add_pushurl_alias(struct remote *remote, const char *url) +{ + const char *pushurl = alias_url(url, &rewrites_push); + if (pushurl != url) + add_pushurl(remote, pushurl); +} + +static void add_url_alias(struct remote *remote, const char *url) +{ + add_url(remote, alias_url(url, &rewrites)); + add_pushurl_alias(remote, url); +} + static struct remote *make_remote(const char *name, int len) { struct remote *ret; @@ -169,22 +181,22 @@ static struct branch *make_branch(const char *name, int len) return ret; } -static struct rewrite *make_rewrite(const char *base, int len) +static struct rewrite *make_rewrite(struct rewrites *r, const char *base, int len) { struct rewrite *ret; int i; - for (i = 0; i < rewrite_nr; i++) { + for (i = 0; i < r->rewrite_nr; i++) { if (len - ? (len == rewrite[i]->baselen && - !strncmp(base, rewrite[i]->base, len)) - : !strcmp(base, rewrite[i]->base)) - return rewrite[i]; + ? (len == r->rewrite[i]->baselen && + !strncmp(base, r->rewrite[i]->base, len)) + : !strcmp(base, r->rewrite[i]->base)) + return r->rewrite[i]; } - ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc); + ALLOC_GROW(r->rewrite, r->rewrite_nr + 1, r->rewrite_alloc); ret = xcalloc(1, sizeof(struct rewrite)); - rewrite[rewrite_nr++] = ret; + r->rewrite[r->rewrite_nr++] = ret; if (len) { ret->base = xstrndup(base, len); ret->baselen = len; @@ -355,8 +367,13 @@ static int handle_config(const char *key, const char *value, void *cb) subkey = strrchr(name, '.'); if (!subkey) return 0; - rewrite = make_rewrite(name, subkey - name); if (!strcmp(subkey, ".insteadof")) { + rewrite = make_rewrite(&rewrites, name, subkey - name); + if (!value) + return config_error_nonbool(key); + add_instead_of(rewrite, xstrdup(value)); + } else if (!strcmp(subkey, ".pushinsteadof")) { + rewrite = make_rewrite(&rewrites_push, name, subkey - name); if (!value) return config_error_nonbool(key); add_instead_of(rewrite, xstrdup(value)); @@ -430,13 +447,17 @@ static void alias_all_urls(void) { int i, j; for (i = 0; i < remotes_nr; i++) { + int add_pushurl_aliases; if (!remotes[i]) continue; - for (j = 0; j < remotes[i]->url_nr; j++) { - remotes[i]->url[j] = alias_url(remotes[i]->url[j]); - } for (j = 0; j < remotes[i]->pushurl_nr; j++) { - remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j]); + remotes[i]->pushurl[j] = alias_url(remotes[i]->pushurl[j], &rewrites); + } + add_pushurl_aliases = remotes[i]->pushurl_nr == 0; + for (j = 0; j < remotes[i]->url_nr; j++) { + if (add_pushurl_aliases) + add_pushurl_alias(remotes[i], remotes[i]->url[j]); + remotes[i]->url[j] = alias_url(remotes[i]->url[j], &rewrites); } } } @@ -1038,7 +1059,7 @@ static int match_explicit(struct ref *src, struct ref *dst, case 0: if (!memcmp(dst_value, "refs/", 5)) matched_dst = make_linked_ref(dst_value, dst_tail); - else if((dst_guess = guess_ref(dst_value, matched_src))) + else if ((dst_guess = guess_ref(dst_value, matched_src))) matched_dst = make_linked_ref(dst_guess, dst_tail); else error("unable to push to unqualified destination: %s\n" diff --git a/replace_object.c b/replace_object.c new file mode 100644 index 000000000..eb59604fd --- /dev/null +++ b/replace_object.c @@ -0,0 +1,114 @@ +#include "cache.h" +#include "sha1-lookup.h" +#include "refs.h" + +static struct replace_object { + unsigned char sha1[2][20]; +} **replace_object; + +static int replace_object_alloc, replace_object_nr; + +static const unsigned char *replace_sha1_access(size_t index, void *table) +{ + struct replace_object **replace = table; + return replace[index]->sha1[0]; +} + +static int replace_object_pos(const unsigned char *sha1) +{ + return sha1_pos(sha1, replace_object, replace_object_nr, + replace_sha1_access); +} + +static int register_replace_object(struct replace_object *replace, + int ignore_dups) +{ + int pos = replace_object_pos(replace->sha1[0]); + + if (0 <= pos) { + if (ignore_dups) + free(replace); + else { + free(replace_object[pos]); + replace_object[pos] = replace; + } + return 1; + } + pos = -pos - 1; + if (replace_object_alloc <= ++replace_object_nr) { + replace_object_alloc = alloc_nr(replace_object_alloc); + replace_object = xrealloc(replace_object, + sizeof(*replace_object) * + replace_object_alloc); + } + if (pos < replace_object_nr) + memmove(replace_object + pos + 1, + replace_object + pos, + (replace_object_nr - pos - 1) * + sizeof(*replace_object)); + replace_object[pos] = replace; + return 0; +} + +static int register_replace_ref(const char *refname, + const unsigned char *sha1, + int flag, void *cb_data) +{ + /* Get sha1 from refname */ + const char *slash = strrchr(refname, '/'); + const char *hash = slash ? slash + 1 : refname; + struct replace_object *repl_obj = xmalloc(sizeof(*repl_obj)); + + if (strlen(hash) != 40 || get_sha1_hex(hash, repl_obj->sha1[0])) { + free(repl_obj); + warning("bad replace ref name: %s", refname); + return 0; + } + + /* Copy sha1 from the read ref */ + hashcpy(repl_obj->sha1[1], sha1); + + /* Register new object */ + if (register_replace_object(repl_obj, 1)) + die("duplicate replace ref: %s", refname); + + return 0; +} + +static void prepare_replace_object(void) +{ + static int replace_object_prepared; + + if (replace_object_prepared) + return; + + for_each_replace_ref(register_replace_ref, NULL); + replace_object_prepared = 1; +} + +/* We allow "recursive" replacement. Only within reason, though */ +#define MAXREPLACEDEPTH 5 + +const unsigned char *lookup_replace_object(const unsigned char *sha1) +{ + int pos, depth = MAXREPLACEDEPTH; + const unsigned char *cur = sha1; + + if (!read_replace_refs) + return sha1; + + prepare_replace_object(); + + /* Try to recursively replace the object */ + do { + if (--depth < 0) + die("replace depth too high for object %s", + sha1_to_hex(sha1)); + + pos = replace_object_pos(cur); + if (0 <= pos) + cur = replace_object[pos]->sha1[1]; + } while (0 <= pos); + + return cur; +} @@ -61,7 +61,7 @@ static int write_rr(struct string_list *rr, int out_fd) path = rr->items[i].string; length = strlen(path) + 1; if (write_in_full(out_fd, rr->items[i].util, 40) != 40 || - write_in_full(out_fd, "\t", 1) != 1 || + write_str_in_full(out_fd, "\t") != 1 || write_in_full(out_fd, path, length) != length) die("unable to write rerere record"); } diff --git a/revision.c b/revision.c index ce24ad9a8..35eca4a36 100644 --- a/revision.c +++ b/revision.c @@ -1664,7 +1664,7 @@ static inline int want_ancestry(struct rev_info *revs) return (revs->rewrite_parents || revs->children.name); } -enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) +enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit) { if (commit->object.flags & SHOWN) return commit_ignore; @@ -1692,12 +1692,23 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) if (!commit->parents || !commit->parents->next) return commit_ignore; } - if (want_ancestry(revs) && rewrite_parents(revs, commit) < 0) - return commit_error; } return commit_show; } +enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit) +{ + enum commit_action action = get_commit_action(revs, commit); + + if (action == commit_show && + !revs->show_all && + revs->prune && revs->dense && want_ancestry(revs)) { + if (rewrite_parents(revs, commit) < 0) + return commit_error; + } + return action; +} + static struct commit *get_revision_1(struct rev_info *revs) { if (!revs->commits) diff --git a/revision.h b/revision.h index b10984b60..9d0dddbcb 100644 --- a/revision.h +++ b/revision.h @@ -168,6 +168,7 @@ enum commit_action { commit_error }; +extern enum commit_action get_commit_action(struct rev_info *revs, struct commit *commit); extern enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit); #endif diff --git a/run-command.c b/run-command.c index ff3d8e2d8..ac314a5a8 100644 --- a/run-command.c +++ b/run-command.c @@ -19,6 +19,7 @@ int start_command(struct child_process *cmd) { int need_in, need_out, need_err; int fdin[2], fdout[2], fderr[2]; + int failed_errno = failed_errno; /* * In case of errors we must keep the promise to close FDs @@ -28,9 +29,10 @@ int start_command(struct child_process *cmd) need_in = !cmd->no_stdin && cmd->in < 0; if (need_in) { if (pipe(fdin) < 0) { + failed_errno = errno; if (cmd->out > 0) close(cmd->out); - return -ERR_RUN_COMMAND_PIPE; + goto fail_pipe; } cmd->in = fdin[1]; } @@ -40,11 +42,12 @@ int start_command(struct child_process *cmd) && cmd->out < 0; if (need_out) { if (pipe(fdout) < 0) { + failed_errno = errno; if (need_in) close_pair(fdin); else if (cmd->in) close(cmd->in); - return -ERR_RUN_COMMAND_PIPE; + goto fail_pipe; } cmd->out = fdout[0]; } @@ -52,6 +55,7 @@ int start_command(struct child_process *cmd) need_err = !cmd->no_stderr && cmd->err < 0; if (need_err) { if (pipe(fderr) < 0) { + failed_errno = errno; if (need_in) close_pair(fdin); else if (cmd->in) @@ -60,7 +64,11 @@ int start_command(struct child_process *cmd) close_pair(fdout); else if (cmd->out) close(cmd->out); - return -ERR_RUN_COMMAND_PIPE; +fail_pipe: + error("cannot create pipe for %s: %s", + cmd->argv[0], strerror(failed_errno)); + errno = failed_errno; + return -1; } cmd->err = fderr[0]; } @@ -122,6 +130,9 @@ int start_command(struct child_process *cmd) strerror(errno)); exit(127); } + if (cmd->pid < 0) + error("cannot fork() for %s: %s", cmd->argv[0], + strerror(failed_errno = errno)); #else int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ const char **sargv = cmd->argv; @@ -162,17 +173,17 @@ int start_command(struct child_process *cmd) if (cmd->dir) die("chdir in start_command() not implemented"); - if (cmd->env) { - env = copy_environ(); - for (; *cmd->env; cmd->env++) - env = env_setenv(env, *cmd->env); - } + if (cmd->env) + env = make_augmented_environ(cmd->env); if (cmd->git_cmd) { cmd->argv = prepare_git_cmd(cmd->argv); } cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env); + failed_errno = errno; + if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT)) + error("cannot spawn %s: %s", cmd->argv[0], strerror(errno)); if (cmd->env) free_environ(env); @@ -189,7 +200,6 @@ int start_command(struct child_process *cmd) #endif if (cmd->pid < 0) { - int err = errno; if (need_in) close_pair(fdin); else if (cmd->in) @@ -200,9 +210,8 @@ int start_command(struct child_process *cmd) close(cmd->out); if (need_err) close_pair(fderr); - return err == ENOENT ? - -ERR_RUN_COMMAND_EXEC : - -ERR_RUN_COMMAND_FORK; + errno = failed_errno; + return -1; } if (need_in) @@ -221,40 +230,51 @@ int start_command(struct child_process *cmd) return 0; } -static int wait_or_whine(pid_t pid) +static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure) { - for (;;) { - int status, code; - pid_t waiting = waitpid(pid, &status, 0); - - if (waiting < 0) { - if (errno == EINTR) - continue; - error("waitpid failed (%s)", strerror(errno)); - return -ERR_RUN_COMMAND_WAITPID; - } - if (waiting != pid) - return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; - if (WIFSIGNALED(status)) - return -ERR_RUN_COMMAND_WAITPID_SIGNAL; - - if (!WIFEXITED(status)) - return -ERR_RUN_COMMAND_WAITPID_NOEXIT; + int status, code = -1; + pid_t waiting; + int failed_errno = 0; + + while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR) + ; /* nothing */ + + if (waiting < 0) { + failed_errno = errno; + error("waitpid for %s failed: %s", argv0, strerror(errno)); + } else if (waiting != pid) { + error("waitpid is confused (%s)", argv0); + } else if (WIFSIGNALED(status)) { + code = WTERMSIG(status); + error("%s died of signal %d", argv0, code); + /* + * This return value is chosen so that code & 0xff + * mimics the exit code that a POSIX shell would report for + * a program that died from this signal. + */ + code -= 128; + } else if (WIFEXITED(status)) { code = WEXITSTATUS(status); - switch (code) { - case 127: - return -ERR_RUN_COMMAND_EXEC; - case 0: - return 0; - default: - return -code; + /* + * Convert special exit code when execvp failed. + */ + if (code == 127) { + code = -1; + failed_errno = ENOENT; + if (!silent_exec_failure) + error("cannot run %s: %s", argv0, + strerror(ENOENT)); } + } else { + error("waitpid is confused (%s)", argv0); } + errno = failed_errno; + return code; } int finish_command(struct child_process *cmd) { - return wait_or_whine(cmd->pid); + return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure); } int run_command(struct child_process *cmd) @@ -274,6 +294,7 @@ static void prepare_run_command_v_opt(struct child_process *cmd, cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0; cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0; cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0; + cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0; } int run_command_v_opt(const char **argv, int opt) @@ -338,10 +359,7 @@ int start_async(struct async *async) int finish_async(struct async *async) { #ifndef __MINGW32__ - int ret = 0; - - if (wait_or_whine(async->pid)) - ret = error("waitpid (async) failed"); + int ret = wait_or_whine(async->pid, "child process", 0); #else DWORD ret = 0; if (WaitForSingleObject(async->tid, INFINITE) != WAIT_OBJECT_0) @@ -385,15 +403,7 @@ int run_hook(const char *index_file, const char *name, ...) hook.env = env; } - ret = start_command(&hook); + ret = run_command(&hook); free(argv); - if (ret) { - warning("Could not spawn %s", argv[0]); - return ret; - } - ret = finish_command(&hook); - if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL) - warning("%s exited due to uncaught signal", argv[0]); - return ret; } diff --git a/run-command.h b/run-command.h index e34550284..0c00b25ff 100644 --- a/run-command.h +++ b/run-command.h @@ -1,17 +1,6 @@ #ifndef RUN_COMMAND_H #define RUN_COMMAND_H -enum { - ERR_RUN_COMMAND_FORK = 10000, - ERR_RUN_COMMAND_EXEC, - ERR_RUN_COMMAND_PIPE, - ERR_RUN_COMMAND_WAITPID, - ERR_RUN_COMMAND_WAITPID_WRONG_PID, - ERR_RUN_COMMAND_WAITPID_SIGNAL, - ERR_RUN_COMMAND_WAITPID_NOEXIT, -}; -#define IS_RUN_COMMAND_ERR(x) (-(x) >= ERR_RUN_COMMAND_FORK) - struct child_process { const char **argv; pid_t pid; @@ -42,6 +31,7 @@ struct child_process { unsigned no_stdout:1; unsigned no_stderr:1; unsigned git_cmd:1; /* if this is to be git sub-command */ + unsigned silent_exec_failure:1; unsigned stdout_to_stderr:1; void (*preexec_cb)(void); }; @@ -55,6 +45,7 @@ extern int run_hook(const char *index_file, const char *name, ...); #define RUN_COMMAND_NO_STDIN 1 #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ #define RUN_COMMAND_STDOUT_TO_STDERR 4 +#define RUN_SILENT_EXEC_FAILURE 8 int run_command_v_opt(const char **argv, int opt); /* diff --git a/send-pack.h b/send-pack.h index 1d7b1b3b4..8b3cf028e 100644 --- a/send-pack.h +++ b/send-pack.h @@ -3,6 +3,7 @@ struct send_pack_args { unsigned verbose:1, + quiet:1, send_mirror:1, force_update:1, use_thin_pack:1, diff --git a/sha1_file.c b/sha1_file.c index 1d996a199..4ea0b18d0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2144,13 +2144,26 @@ static void *read_object(const unsigned char *sha1, enum object_type *type, return read_packed_sha1(sha1, type, size); } -void *read_sha1_file(const unsigned char *sha1, enum object_type *type, - unsigned long *size) +void *read_sha1_file_repl(const unsigned char *sha1, + enum object_type *type, + unsigned long *size, + const unsigned char **replacement) { - void *data = read_object(sha1, type, size); + const unsigned char *repl = lookup_replace_object(sha1); + void *data = read_object(repl, type, size); + + /* die if we replaced an object with one that does not exist */ + if (!data && repl != sha1) + die("replacement %s not found for %s", + sha1_to_hex(repl), sha1_to_hex(sha1)); + /* legacy behavior is to die on corrupted objects */ - if (!data && (has_loose_object(sha1) || has_packed_and_bad(sha1))) - die("object %s is corrupted", sha1_to_hex(sha1)); + if (!data && (has_loose_object(repl) || has_packed_and_bad(repl))) + die("object %s is corrupted", sha1_to_hex(repl)); + + if (replacement) + *replacement = repl; + return data; } @@ -322,7 +322,7 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) return -1; } -int strbuf_getline(struct strbuf *sb, FILE *fp, int term) +int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term) { int ch; @@ -332,10 +332,10 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term) strbuf_reset(sb); while ((ch = fgetc(fp)) != EOF) { - if (ch == term) - break; strbuf_grow(sb, 1); sb->buf[sb->len++] = ch; + if (ch == term) + break; } if (ch == EOF && sb->len == 0) return EOF; @@ -344,6 +344,15 @@ int strbuf_getline(struct strbuf *sb, FILE *fp, int term) return 0; } +int strbuf_getline(struct strbuf *sb, FILE *fp, int term) +{ + if (strbuf_getwholeline(sb, fp, term)) + return EOF; + if (sb->buf[sb->len-1] == term) + strbuf_setlen(sb, sb->len-1); + return 0; +} + int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint) { int fd, len; @@ -126,6 +126,7 @@ extern ssize_t strbuf_read(struct strbuf *, int fd, size_t hint); extern int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint); extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint); +extern int strbuf_getwholeline(struct strbuf *, FILE *, int); extern int strbuf_getline(struct strbuf *, FILE *, int); extern void stripspace(struct strbuf *buf, int skip_comments); diff --git a/t/Makefile b/t/Makefile index bf816fc85..bd09390d3 100644 --- a/t/Makefile +++ b/t/Makefile @@ -3,6 +3,8 @@ # Copyright (c) 2005 Junio C Hamano # +-include ../config.mak + #GIT_TEST_OPTS=--verbose --debug SHELL_PATH ?= $(SHELL) TAR ?= $(TAR) diff --git a/t/gitweb-lib.sh b/t/gitweb-lib.sh new file mode 100644 index 000000000..845253274 --- /dev/null +++ b/t/gitweb-lib.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# +# Copyright (c) 2007 Jakub Narebski +# + +gitweb_init () { + safe_pwd="$(perl -MPOSIX=getcwd -e 'print quotemeta(getcwd)')" + cat >gitweb_config.perl <<EOF +#!/usr/bin/perl + +# gitweb configuration for tests + +our \$version = 'current'; +our \$GIT = 'git'; +our \$projectroot = "$safe_pwd"; +our \$project_maxdepth = 8; +our \$home_link_str = 'projects'; +our \$site_name = '[localhost]'; +our \$site_header = ''; +our \$site_footer = ''; +our \$home_text = 'indextext.html'; +our @stylesheets = ('file:///$TEST_DIRECTORY/../gitweb/gitweb.css'); +our \$logo = 'file:///$TEST_DIRECTORY/../gitweb/git-logo.png'; +our \$favicon = 'file:///$TEST_DIRECTORY/../gitweb/git-favicon.png'; +our \$projects_list = ''; +our \$export_ok = ''; +our \$strict_export = ''; + +EOF + + cat >.git/description <<EOF +$0 test repository +EOF +} + +gitweb_run () { + GATEWAY_INTERFACE='CGI/1.1' + HTTP_ACCEPT='*/*' + REQUEST_METHOD='GET' + SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl" + QUERY_STRING=""$1"" + PATH_INFO=""$2"" + export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \ + SCRIPT_NAME QUERY_STRING PATH_INFO + + GITWEB_CONFIG=$(pwd)/gitweb_config.perl + export GITWEB_CONFIG + + # some of git commands write to STDERR on error, but this is not + # written to web server logs, so we are not interested in that: + # we are interested only in properly formatted errors/warnings + rm -f gitweb.log && + perl -- "$SCRIPT_NAME" \ + >gitweb.output 2>gitweb.log && + if grep '^[[]' gitweb.log >/dev/null 2>&1; then false; else true; fi + + # gitweb.log is left for debugging + # gitweb.output is used to parse http output +} + +. ./test-lib.sh + +if ! test_have_prereq PERL; then + say 'skipping gitweb tests, perl not available' + test_done +fi + +perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { + say 'skipping gitweb tests, perl version is too old' + test_done +} + +gitweb_init diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh new file mode 100644 index 000000000..4b3b79373 --- /dev/null +++ b/t/lib-cvs.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +. ./test-lib.sh + +unset CVS_SERVER +# for clean cvsps cache +HOME=$(pwd) +export HOME + +if ! type cvs >/dev/null 2>&1 +then + say 'skipping cvsimport tests, cvs not found' + test_done +fi + +CVS="cvs -f" +export CVS + +cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'` +case "$cvsps_version" in +2.1 | 2.2*) + ;; +'') + say 'skipping cvsimport tests, cvsps not found' + test_done + ;; +*) + say 'skipping cvsimport tests, unsupported cvsps version' + test_done + ;; +esac + +test_cvs_co () { + # Usage: test_cvs_co BRANCH_NAME + rm -rf module-cvs-"$1" + if [ "$1" = "master" ] + then + $CVS co -P -d module-cvs-"$1" -A module + else + $CVS co -P -d module-cvs-"$1" -r "$1" module + fi +} + +test_git_co () { + # Usage: test_git_co BRANCH_NAME + (cd module-git && git checkout "$1") +} + +test_cmp_branch_file () { + # Usage: test_cmp_branch_file BRANCH_NAME PATH + # The branch must already be checked out of CVS and git. + test_cmp module-cvs-"$1"/"$2" module-git/"$2" +} + +test_cmp_branch_tree () { + # Usage: test_cmp_branch_tree BRANCH_NAME + # Check BRANCH_NAME out of CVS and git and make sure that all + # of the files and directories are identical. + + test_cvs_co "$1" && + test_git_co "$1" && + ( + cd module-cvs-"$1" + find . -type d -name CVS -prune -o -type f -print + ) | sort >module-cvs-"$1".list && + ( + cd module-git + find . -type d -name .git -prune -o -type f -print + ) | sort >module-git-"$1".list && + test_cmp module-cvs-"$1".list module-git-"$1".list && + cat module-cvs-"$1".list | while read f + do + test_cmp_branch_file "$1" "$f" || return 1 + done +} diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 565496234..fd8631f90 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -14,7 +14,7 @@ if ! test_have_prereq PERL; then fi GIT_DIR=$PWD/.git -GIT_SVN_DIR=$GIT_DIR/svn/git-svn +GIT_SVN_DIR=$GIT_DIR/svn/refs/remotes/git-svn SVN_TREE=$GIT_SVN_DIR/svn-tree svn >/dev/null 2>&1 diff --git a/t/lib-patch-mode.sh b/t/lib-patch-mode.sh new file mode 100755 index 000000000..75a3ee283 --- /dev/null +++ b/t/lib-patch-mode.sh @@ -0,0 +1,41 @@ +. ./test-lib.sh + +if ! test_have_prereq PERL; then + say 'skipping --patch tests, perl not available' + test_done +fi + +set_state () { + echo "$3" > "$1" && + git add "$1" && + echo "$2" > "$1" +} + +save_state () { + noslash="$(echo "$1" | tr / _)" && + cat "$1" > _worktree_"$noslash" && + git show :"$1" > _index_"$noslash" +} + +set_and_save_state () { + set_state "$@" && + save_state "$1" +} + +verify_state () { + test "$(cat "$1")" = "$2" && + test "$(git show :"$1")" = "$3" +} + +verify_saved_state () { + noslash="$(echo "$1" | tr / _)" && + verify_state "$1" "$(cat _worktree_"$noslash")" "$(cat _index_"$noslash")" +} + +save_head () { + git rev-parse HEAD > _head +} + +verify_saved_head () { + test "$(cat _head)" = "$(git rev-parse HEAD)" +} diff --git a/t/t0001-init.sh b/t/t0001-init.sh index e3d846420..538650479 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -208,4 +208,87 @@ test_expect_success 'init rejects insanely long --template' ' ) ' +test_expect_success 'init creates a new directory' ' + rm -fr newdir && + ( + git init newdir && + test -d newdir/.git/refs + ) +' + +test_expect_success 'init creates a new bare directory' ' + rm -fr newdir && + ( + git init --bare newdir && + test -d newdir/refs + ) +' + +test_expect_success 'init recreates a directory' ' + rm -fr newdir && + ( + mkdir newdir && + git init newdir && + test -d newdir/.git/refs + ) +' + +test_expect_success 'init recreates a new bare directory' ' + rm -fr newdir && + ( + mkdir newdir && + git init --bare newdir && + test -d newdir/refs + ) +' + +test_expect_success 'init creates a new deep directory' ' + rm -fr newdir && + git init newdir/a/b/c && + test -d newdir/a/b/c/.git/refs +' + +test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shared)' ' + rm -fr newdir && + ( + # Leading directories should honor umask while + # the repository itself should follow "shared" + umask 002 && + git init --bare --shared=0660 newdir/a/b/c && + test -d newdir/a/b/c/refs && + ls -ld newdir/a newdir/a/b > lsab.out && + ! grep -v "^drwxrw[sx]r-x" lsab.out && + ls -ld newdir/a/b/c > lsc.out && + ! grep -v "^drwxrw[sx]---" lsc.out + ) +' + +test_expect_success 'init notices EEXIST (1)' ' + rm -fr newdir && + ( + >newdir && + test_must_fail git init newdir && + test -f newdir + ) +' + +test_expect_success 'init notices EEXIST (2)' ' + rm -fr newdir && + ( + mkdir newdir && + >newdir/a + test_must_fail git init newdir/a/b && + test -f newdir/a + ) +' + +test_expect_success POSIXPERM 'init notices EPERM' ' + rm -fr newdir && + ( + mkdir newdir && + chmod -w newdir && + test_must_fail git init newdir/a/b + ) +' + test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh new file mode 100755 index 000000000..a4d8fa8fa --- /dev/null +++ b/t/t0006-date.sh @@ -0,0 +1,75 @@ +#!/bin/sh + +test_description='test date parsing and printing' +. ./test-lib.sh + +# arbitrary reference time: 2009-08-30 19:20:00 +TEST_DATE_NOW=1251660000; export TEST_DATE_NOW + +check_show() { + t=$(($TEST_DATE_NOW - $1)) + echo "$t -> $2" >expect + test_expect_${3:-success} "relative date ($2)" " + test-date show $t >actual && + test_cmp expect actual + " +} + +check_show 5 '5 seconds ago' +check_show 300 '5 minutes ago' +check_show 18000 '5 hours ago' +check_show 432000 '5 days ago' +check_show 1728000 '3 weeks ago' +check_show 13000000 '5 months ago' +check_show 37500000 '1 year, 2 months ago' +check_show 55188000 '1 year, 9 months ago' +check_show 630000000 '20 years ago' + +check_parse() { + echo "$1 -> $2" >expect + test_expect_${3:-success} "parse date ($1)" " + test-date parse '$1' >actual && + test_cmp expect actual + " +} + +check_parse 2008 bad +check_parse 2008-02 bad +check_parse 2008-02-14 bad +check_parse '2008-02-14 20:30:45' '2008-02-14 20:30:45 +0000' + +check_approxidate() { + echo "$1 -> $2 +0000" >expect + test_expect_${3:-success} "parse approxidate ($1)" " + test-date approxidate '$1' >actual && + test_cmp expect actual + " +} + +check_approxidate now '2009-08-30 19:20:00' +check_approxidate '5 seconds ago' '2009-08-30 19:19:55' +check_approxidate 5.seconds.ago '2009-08-30 19:19:55' +check_approxidate 10.minutes.ago '2009-08-30 19:10:00' +check_approxidate yesterday '2009-08-29 19:20:00' +check_approxidate 3.days.ago '2009-08-27 19:20:00' +check_approxidate 3.weeks.ago '2009-08-09 19:20:00' +check_approxidate 3.months.ago '2009-05-30 19:20:00' +check_approxidate 2.years.3.months.ago '2007-05-30 19:20:00' + +check_approxidate '6am yesterday' '2009-08-29 06:00:00' +check_approxidate '6pm yesterday' '2009-08-29 18:00:00' +check_approxidate '3:00' '2009-08-30 03:00:00' +check_approxidate '15:00' '2009-08-30 15:00:00' +check_approxidate 'noon today' '2009-08-30 12:00:00' +check_approxidate 'noon yesterday' '2009-08-29 12:00:00' + +check_approxidate 'last tuesday' '2009-08-25 19:20:00' +check_approxidate 'July 5th' '2009-07-05 19:20:00' +check_approxidate '06/05/2009' '2009-06-05 19:20:00' +check_approxidate '06.05.2009' '2009-05-06 19:20:00' + +check_approxidate 'Jun 6, 5AM' '2009-06-06 05:00:00' +check_approxidate '5AM Jun 6' '2009-06-06 05:00:00' +check_approxidate '6AM, June 7, 2009' '2009-06-07 06:00:00' + +test_done diff --git a/t/t1009-read-tree-new-index.sh b/t/t1009-read-tree-new-index.sh new file mode 100755 index 000000000..59b3aa4bc --- /dev/null +++ b/t/t1009-read-tree-new-index.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +test_description='test read-tree into a fresh index file' + +. ./test-lib.sh + +test_expect_success setup ' + echo one >a && + git add a && + git commit -m initial +' + +test_expect_success 'non-existent index file' ' + rm -f new-index && + GIT_INDEX_FILE=new-index git read-tree master +' + +test_expect_success 'empty index file' ' + rm -f new-index && + > new-index && + GIT_INDEX_FILE=new-index git read-tree master +' + +test_done + diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh new file mode 100755 index 000000000..4d1c2e9e0 --- /dev/null +++ b/t/t2016-checkout-patch.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +test_description='git checkout --patch' + +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add bar dir/foo && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work head && + (echo n; echo n) | git checkout -p && + verify_saved_state bar && + verify_saved_state dir/foo +' + +test_expect_success 'git checkout -p' ' + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p with staged changes' ' + set_state dir/foo work index + (echo n; echo y) | git checkout -p && + verify_saved_state bar && + verify_state dir/foo index index +' + +test_expect_success 'git checkout -p HEAD with NO staged changes: abort' ' + set_and_save_state dir/foo work head && + (echo n; echo y; echo n) | git checkout -p HEAD && + verify_saved_state bar && + verify_saved_state dir/foo +' + +test_expect_success 'git checkout -p HEAD with NO staged changes: apply' ' + (echo n; echo y; echo y) | git checkout -p HEAD && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p HEAD with change already staged' ' + set_state dir/foo index index + # the third n is to get out in case it mistakenly does not apply + (echo n; echo y; echo n) | git checkout -p HEAD && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'git checkout -p HEAD^' ' + # the third n is to get out in case it mistakenly does not apply + (echo n; echo y; echo n) | git checkout -p HEAD^ && + verify_saved_state bar && + verify_state dir/foo parent parent +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'path limiting works: dir' ' + set_state dir/foo work head && + (echo y; echo n) | git checkout -p dir && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'path limiting works: -- dir' ' + set_state dir/foo work head && + (echo y; echo n) | git checkout -p -- dir && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'path limiting works: HEAD^ -- dir' ' + # the third n is to get out in case it mistakenly does not apply + (echo y; echo n; echo n) | git checkout -p HEAD^ -- dir && + verify_saved_state bar && + verify_state dir/foo parent parent +' + +test_expect_success 'path limiting works: foo inside dir' ' + set_state dir/foo work head && + # the third n is to get out in case it mistakenly does not apply + (echo y; echo n; echo n) | (cd dir && git checkout -p foo) && + verify_saved_state bar && + verify_state dir/foo head head +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + +test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index c5c29ccc4..4e6a44b62 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -3,9 +3,10 @@ # Copyright (c) 2005 Amos Waterland # -test_description='git rebase should not destroy author information +test_description='git rebase assorted tests -This test runs git rebase and checks that the author information is not lost. +This test runs git rebase and checks that the author information is not lost +among other things. ' . ./test-lib.sh @@ -133,4 +134,25 @@ test_expect_success 'rebase -q is quiet' ' test ! -s output.out ' +q_to_cr () { + tr Q '\015' +} + +test_expect_success 'Rebase a commit that sprinkles CRs in' ' + ( + echo "One" + echo "TwoQ" + echo "Three" + echo "FQur" + echo "Five" + ) | q_to_cr >CR && + git add CR && + test_tick && + git commit -a -m "A file with a line with CR" && + git tag file-with-cr && + git checkout HEAD^0 && + git rebase --onto HEAD^^ HEAD^ && + git diff --exit-code file-with-cr:CR HEAD:CR +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index a973628e8..4cae01952 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -10,7 +10,7 @@ that the result still makes sense. ' . ./test-lib.sh -. ../lib-rebase.sh +. "$TEST_DIRECTORY"/lib-rebase.sh set_fake_editor diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh index 653350521..14a23cd87 100755 --- a/t/t3411-rebase-preserve-around-merges.sh +++ b/t/t3411-rebase-preserve-around-merges.sh @@ -10,7 +10,7 @@ a merge to before the merge. ' . ./test-lib.sh -. ../lib-rebase.sh +. "$TEST_DIRECTORY"/lib-rebase.sh set_fake_editor diff --git a/t/t3414-rebase-preserve-onto.sh b/t/t3414-rebase-preserve-onto.sh index 80019ee07..ee0a6cccf 100755 --- a/t/t3414-rebase-preserve-onto.sh +++ b/t/t3414-rebase-preserve-onto.sh @@ -10,7 +10,7 @@ aren'"'"'t on top of $ONTO, even if they are on top of $UPSTREAM. ' . ./test-lib.sh -. ../lib-rebase.sh +. "$TEST_DIRECTORY"/lib-rebase.sh # Set up branches like this: # A1---B1---E1---F1---G1 diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 7a3fb6795..5514f74b3 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,4 +200,23 @@ test_expect_success 'drop -q is quiet' ' test ! -s output.out ' +test_expect_success 'stash -k' ' + echo bar3 > file && + echo bar4 > file2 && + git add file2 && + git stash -k && + test bar,bar4 = $(cat file),$(cat file2) +' + +test_expect_success 'stash --invalid-option' ' + echo bar5 > file && + echo bar6 > file2 && + git add file2 && + test_must_fail git stash --invalid-option && + test_must_fail git stash save --invalid-option && + test bar5,bar6 = $(cat file),$(cat file2) && + git stash -- -message-starting-with-dash && + test bar,bar2 = $(cat file),$(cat file2) +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh new file mode 100755 index 000000000..f37e3bc6e --- /dev/null +++ b/t/t3904-stash-patch.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='git checkout --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add bar dir/foo && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + echo index > dir/foo && + git add dir/foo && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before dir, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_state dir/foo work index + (echo n; echo n) | test_must_fail git stash save -p && + verify_state dir/foo work index && + verify_saved_state bar +' + +test_expect_success 'git stash -p' ' + (echo n; echo y) | git stash save -p && + verify_state dir/foo head index && + verify_saved_state bar && + git reset --hard && + git stash apply && + verify_state dir/foo work head && + verify_state bar dummy dummy +' + +test_expect_success 'git stash -p --no-keep-index' ' + set_state dir/foo work index && + set_state bar bar_work bar_index && + (echo n; echo y) | git stash save -p --no-keep-index && + verify_state dir/foo head head && + verify_state bar bar_work dummy && + git reset --hard && + git stash apply --index && + verify_state dir/foo work index && + verify_state bar dummy bar_index +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + +test_done diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 922a8941e..531f5b795 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -493,13 +493,12 @@ test_expect_success 'format-patch from a subdirectory (2)' ' ' test_expect_success 'format-patch from a subdirectory (3)' ' - here="$TEST_DIRECTORY/$test" && rm -f 0* && filename=$( rm -rf sub && mkdir -p sub/dir && cd sub/dir && - git format-patch -1 -o "$here" + git format-patch -1 -o "$TRASH_DIRECTORY" ) && basename=$(expr "$filename" : ".*/\(.*\)") && test -f "$basename" diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 4ea42e00d..a7602cf92 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -166,7 +166,7 @@ test_expect_success 'diff --cached' ' git update-index --assume-unchanged file && echo second >file && git diff --cached >actual && - test_cmp ../t4020/diff.NUL actual + test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual ' test_done diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh new file mode 100755 index 000000000..9d9498bd9 --- /dev/null +++ b/t/t4039-diff-assume-unchanged.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='diff with assume-unchanged entries' + +. ./test-lib.sh + +# external diff has been tested in t4020-diff-external.sh + +test_expect_success 'setup' ' + echo zero > zero && + git add zero && + git commit -m zero && + echo one > one && + echo two > two && + git add one two && + git commit -m onetwo && + git update-index --assume-unchanged one && + echo borked >> one && + test "$(git ls-files -v one)" = "h one" +' + +test_expect_success 'diff-index does not examine assume-unchanged entries' ' + git diff-index HEAD^ -- one | grep -q 5626abf0f72e58d7a153368ba57db4c673c0e171 +' + +test_expect_success 'diff-files does not examine assume-unchanged entries' ' + rm one && + test -z "$(git diff-files -- one)" +' + +test_done diff --git a/t/t4107-apply-ignore-whitespace.sh b/t/t4107-apply-ignore-whitespace.sh new file mode 100755 index 000000000..484654d6e --- /dev/null +++ b/t/t4107-apply-ignore-whitespace.sh @@ -0,0 +1,185 @@ +#!/bin/sh +# +# Copyright (c) 2009 Giuseppe Bilotta +# + +test_description='git-apply --ignore-whitespace. + +' +. ./test-lib.sh + +# This primes main.c file that indents without using HT at all. +# Various patches with HT and other spaces are attempted in the test. + +cat > patch1.patch <<\EOF +diff --git a/main.c b/main.c +new file mode 100644 +--- /dev/null ++++ b/main.c +@@ -0,0 +1,22 @@ ++#include <stdio.h> ++ ++void print_int(int num); ++int func(int num); ++ ++int main() { ++ int i; ++ ++ for (i = 0; i < 10; i++) { ++ print_int(func(i)); /* stuff */ ++ } ++ ++ return 0; ++} ++ ++int func(int num) { ++ return num * num; ++} ++ ++void print_int(int num) { ++ printf("%d", num); ++} +EOF + +# Since whitespace is very significant and we want to prevent whitespace +# mangling when creating this test from a patch, we protect 'fixable' +# whitespace by replacing spaces with Z and replacing them at patch +# creation time, hence the sed trick. + +# This patch will fail unless whitespace differences are being ignored + +sed -e 's/Z/ /g' > patch2.patch <<\EOF +diff --git a/main.c b/main.c +--- a/main.c ++++ b/main.c +@@ -10,6 +10,8 @@ +Z print_int(func(i)); /* stuff */ +Z } +Z ++ printf("\n"); ++ +Z return 0; +Z} +Z +EOF + +# This patch will fail even if whitespace differences are being ignored, +# because of the missing string at EOL. TODO: this testcase should be +# improved by creating a line that has the same hash with and without +# the final string. + +sed -e 's/Z/ /g' > patch3.patch <<\EOF +diff --git a/main.c b/main.c +--- a/main.c ++++ b/main.c +@@ -10,3 +10,4 @@ +Z for (i = 0; i < 10; i++) { +Z print_int(func(i));Z ++ /* stuff */ +Z } +EOF + +# This patch will fail even if whitespace differences are being ignored, +# because of the missing EOL at EOF. + +sed -e 's/Z/ /g' > patch4.patch <<\EOF +diff --git a/main.c b/main.c +--- a/main.c ++++ b/main.c +@@ -21,1 +21,1 @@ +- };Z +\ No newline at end of file ++ }; +EOF + +# This patch will fail unless whitespace differences are being ignored. + +sed -e 's/Z/ /g' > patch5.patch <<\EOF +diff --git a/main.c b/main.c +--- a/main.c ++++ b/main.c +@@ -2,2 +2,3 @@ +Z void print_int(int num); ++ /* a comment */ +Z int func(int num); +EOF + +# And this is how the final output should be. Patches introduce +# HTs but the original SP indents are mostly kept. + +sed -e 's/T/ /g' > main.c.final <<\EOF +#include <stdio.h> + +void print_int(int num); +T/* a comment */ +int func(int num); + +int main() { + int i; + + for (i = 0; i < 10; i++) { + print_int(func(i)); /* stuff */ + } + +Tprintf("\n"); + + return 0; +} + +int func(int num) { + return num * num; +} + +void print_int(int num) { + printf("%d", num); +} +EOF + +test_expect_success 'file creation' ' + git-apply patch1.patch +' + +test_expect_success 'patch2 fails (retab)' ' + test_must_fail git-apply patch2.patch +' + +test_expect_success 'patch2 applies with --ignore-whitespace' ' + git-apply --ignore-whitespace patch2.patch +' + +test_expect_success 'patch2 reverse applies with --ignore-space-change' ' + git-apply -R --ignore-space-change patch2.patch +' + +git config apply.ignorewhitespace change + +test_expect_success 'patch2 applies (apply.ignorewhitespace = change)' ' + git-apply patch2.patch +' + +test_expect_success 'patch3 fails (missing string at EOL)' ' + test_must_fail git-apply patch3.patch +' + +test_expect_success 'patch4 fails (missing EOL at EOF)' ' + test_must_fail git-apply patch4.patch +' + +test_expect_success 'patch5 applies (leading whitespace)' ' + git-apply patch5.patch +' + +test_expect_success 'patches do not mangle whitespace' ' + test_cmp main.c main.c.final +' + +test_expect_success 're-create file (with --ignore-whitespace)' ' + rm -f main.c && + git-apply patch1.patch +' + +test_expect_success 'patch5 fails (--no-ignore-whitespace)' ' + test_must_fail git-apply --no-ignore-whitespace patch5.patch +' + +test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index e70ea94a1..0279d07c8 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,18 +11,26 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 13' + test `cat last` = 14' + +check_mailinfo () { + mail=$1 opt=$2 + mo="$mail$opt" + git mailinfo -u $opt msg$mo patch$mo <$mail >info$mo && + test_cmp "$TEST_DIRECTORY"/t5100/msg$mo msg$mo && + test_cmp "$TEST_DIRECTORY"/t5100/patch$mo patch$mo && + test_cmp "$TEST_DIRECTORY"/t5100/info$mo info$mo +} + for mail in `echo 00*` do test_expect_success "mailinfo $mail" ' - git mailinfo -u msg$mail patch$mail <$mail >info$mail && - echo msg && - test_cmp "$TEST_DIRECTORY"/t5100/msg$mail msg$mail && - echo patch && - test_cmp "$TEST_DIRECTORY"/t5100/patch$mail patch$mail && - echo info && - test_cmp "$TEST_DIRECTORY"/t5100/info$mail info$mail + check_mailinfo $mail "" && + if test -f "$TEST_DIRECTORY"/t5100/msg$mail--scissors + then + check_mailinfo $mail --scissors + fi ' done diff --git a/t/t5100/info0014 b/t/t5100/info0014 new file mode 100644 index 000000000..08566b34b --- /dev/null +++ b/t/t5100/info0014 @@ -0,0 +1,5 @@ +Author: Junio Hamano +Email: junkio@cox.net +Subject: BLAH ONE +Date: Thu, 20 Aug 2009 17:18:22 -0700 + diff --git a/t/t5100/info0014--scissors b/t/t5100/info0014--scissors new file mode 100644 index 000000000..ab9c8d090 --- /dev/null +++ b/t/t5100/info0014--scissors @@ -0,0 +1,5 @@ +Author: Junio C Hamano +Email: gitster@pobox.com +Subject: Teach mailinfo to ignore everything before -- >8 -- mark +Date: Thu, 20 Aug 2009 17:18:22 -0700 + diff --git a/t/t5100/msg0014 b/t/t5100/msg0014 new file mode 100644 index 000000000..62e5cd2ec --- /dev/null +++ b/t/t5100/msg0014 @@ -0,0 +1,18 @@ +In real life, we will see a discussion that inspired this patch +discussing related and unrelated things around >8 scissors mark +in this part of the message. + +Subject: [PATCH] BLAH TWO + +And then we will see the scissors. + + This line is not a scissors mark -- >8 -- but talks about it. + - - >8 - - please remove everything above this line - - >8 - - + +Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark +From: Junio C Hamano <gitster@pobox.com> + +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/t/t5100/msg0014--scissors b/t/t5100/msg0014--scissors new file mode 100644 index 000000000..259c6a46d --- /dev/null +++ b/t/t5100/msg0014--scissors @@ -0,0 +1,4 @@ +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> diff --git a/t/t5100/patch0014 b/t/t5100/patch0014 new file mode 100644 index 000000000..124efd234 --- /dev/null +++ b/t/t5100/patch0014 @@ -0,0 +1,64 @@ +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5100/patch0014--scissors b/t/t5100/patch0014--scissors new file mode 100644 index 000000000..124efd234 --- /dev/null +++ b/t/t5100/patch0014--scissors @@ -0,0 +1,64 @@ +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index c3074ac57..13fa4ae03 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -561,3 +561,92 @@ From: <a.u.thor@example.com> (A U Thor) Date: Fri, 9 Jun 2006 00:44:16 -0700 Subject: [PATCH] a patch +From nobody Mon Sep 17 00:00:00 2001 +From: Junio Hamano <junkio@cox.net> +Date: Thu, 20 Aug 2009 17:18:22 -0700 +Subject: Why doesn't git-am does not like >8 scissors mark? + +Subject: [PATCH] BLAH ONE + +In real life, we will see a discussion that inspired this patch +discussing related and unrelated things around >8 scissors mark +in this part of the message. + +Subject: [PATCH] BLAH TWO + +And then we will see the scissors. + + This line is not a scissors mark -- >8 -- but talks about it. + - - >8 - - please remove everything above this line - - >8 - - + +Subject: [PATCH] Teach mailinfo to ignore everything before -- >8 -- mark +From: Junio C Hamano <gitster@pobox.com> + +This teaches mailinfo the scissors -- >8 -- mark; the command ignores +everything before it in the message body. + +Signed-off-by: Junio C Hamano <gitster@pobox.com> +--- + builtin-mailinfo.c | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c +index b0b5d8f..461c47e 100644 +--- a/builtin-mailinfo.c ++++ b/builtin-mailinfo.c +@@ -712,6 +712,34 @@ static inline int patchbreak(const struct strbuf *line) + return 0; + } + ++static int scissors(const struct strbuf *line) ++{ ++ size_t i, len = line->len; ++ int scissors_dashes_seen = 0; ++ const char *buf = line->buf; ++ ++ for (i = 0; i < len; i++) { ++ if (isspace(buf[i])) ++ continue; ++ if (buf[i] == '-') { ++ scissors_dashes_seen |= 02; ++ continue; ++ } ++ if (i + 1 < len && !memcmp(buf + i, ">8", 2)) { ++ scissors_dashes_seen |= 01; ++ i++; ++ continue; ++ } ++ if (i + 7 < len && !memcmp(buf + i, "cut here", 8)) { ++ i += 7; ++ continue; ++ } ++ /* everything else --- not scissors */ ++ break; ++ } ++ return scissors_dashes_seen == 03; ++} ++ + static int handle_commit_msg(struct strbuf *line) + { + static int still_looking = 1; +@@ -723,10 +751,17 @@ static int handle_commit_msg(struct strbuf *line) + strbuf_ltrim(line); + if (!line->len) + return 0; +- if ((still_looking = check_header(line, s_hdr_data, 0)) != 0) ++ still_looking = check_header(line, s_hdr_data, 0); ++ if (still_looking) + return 0; + } + ++ if (scissors(line)) { ++ fseek(cmitmsg, 0L, SEEK_SET); ++ still_looking = 1; ++ return 0; ++ } ++ + /* normalize the log message to UTF-8. */ + if (metainfo_charset) + convert_to_utf8(line, charset.buf); +-- +1.6.4.1 diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 55ed7c793..3c6687abe 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -6,6 +6,17 @@ test_description='prune' . ./test-lib.sh +day=$((60*60*24)) +week=$(($day*7)) + +add_blob() { + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE +} + test_expect_success setup ' : > file && @@ -31,11 +42,7 @@ test_expect_success 'prune stale packs' ' test_expect_success 'prune --expire' ' - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && + add_blob && git prune --expire=1.hour.ago && test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test -f $BLOB_FILE && @@ -48,16 +55,12 @@ test_expect_success 'prune --expire' ' test_expect_success 'gc: implicit prune --expire' ' - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph_0 | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && - test-chmtime =-$((86400*14-30)) $BLOB_FILE && + add_blob && + test-chmtime =-$((2*$week-30)) $BLOB_FILE && git gc && test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && test -f $BLOB_FILE && - test-chmtime =-$((86400*14+1)) $BLOB_FILE && + test-chmtime =-$((2*$week+1)) $BLOB_FILE && git gc && test $before = $(git count-objects | sed "s/ .*//") && ! test -f $BLOB_FILE @@ -114,12 +117,8 @@ test_expect_success 'prune: do not prune heads listed as an argument' ' test_expect_success 'gc --no-prune' ' - before=$(git count-objects | sed "s/ .*//") && - BLOB=$(echo aleph_0 | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && - test -f $BLOB_FILE && - test-chmtime =-$((86400*5001)) $BLOB_FILE && + add_blob && + test-chmtime =-$((5001*$day)) $BLOB_FILE && git config gc.pruneExpire 2.days.ago && git gc --no-prune && test 1 = $(git count-objects | sed "s/ .*//") && @@ -140,9 +139,8 @@ test_expect_success 'gc respects gc.pruneExpire' ' test_expect_success 'gc --prune=<date>' ' - BLOB=$(echo aleph_0 | git hash-object -w --stdin) && - BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && - test-chmtime =-$((86400*5001)) $BLOB_FILE && + add_blob && + test-chmtime =-$((5001*$day)) $BLOB_FILE && git gc --prune=5002.days.ago && test -f $BLOB_FILE && git gc --prune=5000.days.ago && @@ -150,4 +148,18 @@ test_expect_success 'gc --prune=<date>' ' ' +test_expect_success 'gc: prune old objects after local clone' ' + add_blob && + test-chmtime =-$((2*$week+1)) $BLOB_FILE && + git clone --no-hardlinks . aclone && + ( + cd aclone && + test 1 = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + git gc --prune && + test 0 = $(git count-objects | sed "s/ .*//") && + ! test -f $BLOB_FILE + ) +' + test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index a8c2ca2a7..18376d660 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -139,6 +139,36 @@ test_expect_success 'fsck in shallow repo' ' ) ' +test_expect_success 'simple fetch in shallow repo' ' + ( + cd shallow && + git fetch + ) +' + +test_expect_success 'no changes expected' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow.2 && + cmp count.shallow count.shallow.2 +' + +test_expect_success 'fetch same depth in shallow repo' ' + ( + cd shallow && + git fetch --depth=2 + ) +' + +test_expect_success 'no changes expected' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow.3 && + cmp count.shallow count.shallow.3 +' + test_expect_success 'add two more' ' add B66 $B65 && add B67 $B66 @@ -201,4 +231,21 @@ test_expect_success 'pull in shallow repo with missing merge base' ' ) ' +test_expect_success 'additional simple shallow deepenings' ' + ( + cd shallow && + git fetch --depth=8 && + git fetch --depth=10 && + git fetch --depth=11 + ) +' + +test_expect_success 'clone shallow object count' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow && + grep "^count: 52" count.shallow +' + test_done diff --git a/t/t5501-post-upload-pack.sh b/t/t5501-post-upload-pack.sh new file mode 100755 index 000000000..d89fb51ba --- /dev/null +++ b/t/t5501-post-upload-pack.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='post upload-hook' + +. ./test-lib.sh + +LOGFILE=".git/post-upload-pack-log" + +test_expect_success setup ' + test_commit A && + test_commit B && + git reset --hard A && + test_commit C && + git branch prev B && + mkdir -p .git/hooks && + { + echo "#!$SHELL_PATH" && + echo "cat >post-upload-pack-log" + } >".git/hooks/post-upload-pack" && + chmod +x .git/hooks/post-upload-pack +' + +test_expect_success initial ' + rm -fr sub && + git init sub && + ( + cd sub && + git fetch --no-tags .. prev + ) && + want=$(sed -n "s/^want //p" "$LOGFILE") && + test "$want" = "$(git rev-parse --verify B)" && + ! grep "^have " "$LOGFILE" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = fetch +' + +test_expect_success second ' + rm -fr sub && + git init sub && + ( + cd sub && + git fetch --no-tags .. prev:refs/remotes/prev && + git fetch --no-tags .. master + ) && + want=$(sed -n "s/^want //p" "$LOGFILE") && + test "$want" = "$(git rev-parse --verify C)" && + have=$(sed -n "s/^have //p" "$LOGFILE") && + test "$have" = "$(git rev-parse --verify B)" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = fetch +' + +test_expect_success all ' + rm -fr sub && + HERE=$(pwd) && + git init sub && + ( + cd sub && + git clone "file://$HERE/.git" new + ) && + sed -n "s/^want //p" "$LOGFILE" | sort >actual && + git rev-parse A B C | sort >expect && + test_cmp expect actual && + ! grep "^have " "$LOGFILE" && + kind=$(sed -n "s/^kind //p" "$LOGFILE") && + test "$kind" = clone +' + +test_done diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2d2633f3f..6889a53cf 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -122,6 +122,23 @@ test_expect_success 'fetch with insteadOf' ' ) ' +test_expect_success 'fetch with pushInsteadOf (should not rewrite)' ' + mk_empty && + ( + TRASH=$(pwd)/ && + cd testrepo && + git config "url.trash/.pushInsteadOf" "$TRASH" && + git config remote.up.url "$TRASH." && + git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" && + git fetch up && + + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push without wildcard' ' mk_empty && @@ -162,6 +179,36 @@ test_expect_success 'push with insteadOf' ' ) ' +test_expect_success 'push with pushInsteadOf' ' + mk_empty && + TRASH="$(pwd)/" && + git config "url.$TRASH.pushInsteadOf" trash/ && + git push trash/testrepo refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + +test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' ' + mk_empty && + TRASH="$(pwd)/" && + git config "url.trash2/.pushInsteadOf" trash/ && + git config remote.r.url trash/wrong && + git config remote.r.pushurl "$TRASH/testrepo" && + git push r refs/heads/master:refs/remotes/origin/master && + ( + cd testrepo && + r=$(git show-ref -s --verify refs/remotes/origin/master) && + test "z$r" = "z$the_commit" && + + test 1 = $(git for-each-ref refs/remotes/origin | wc -l) + ) +' + test_expect_success 'push with matching heads' ' mk_test heads/master && diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index e78d40242..dd2ee842e 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -149,4 +149,15 @@ test_expect_success 'pull --rebase dies early with dirty working directory' ' ' +test_expect_success 'pull --rebase works on branch yet to be born' ' + git rev-parse master >expect && + mkdir empty_repo && + (cd empty_repo && + git init && + git pull --rebase .. master && + git rev-parse HEAD >../actual + ) && + test_cmp expect actual +' + test_done diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index f5102b902..a696b8791 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -30,11 +30,12 @@ test_expect_success 'fsck fails' ' test_must_fail git fsck ' -test_expect_success 'upload-pack fails due to error in pack-objects' ' +test_expect_success 'upload-pack fails due to error in pack-objects packing' ' ! echo "0032want $(git rev-parse HEAD) 00000009done 0000" | git upload-pack . > /dev/null 2> output.err && + grep "unable to read" output.err && grep "pack-objects died" output.err ' @@ -51,9 +52,21 @@ test_expect_success 'fsck fails' ' test_expect_success 'upload-pack fails due to error in rev-list' ' ! echo "0032want $(git rev-parse HEAD) +0034shallow $(git rev-parse HEAD^)00000009done +0000" | git upload-pack . > /dev/null 2> output.err && + # pack-objects survived + grep "Total.*, reused" output.err && + # but there was an error, which must have been in rev-list + grep "bad tree object" output.err +' + +test_expect_success 'upload-pack fails due to error in pack-objects enumeration' ' + + ! echo "0032want $(git rev-parse HEAD) 00000009done 0000" | git upload-pack . > /dev/null 2> output.err && - grep "waitpid (async) failed" output.err + grep "bad tree object" output.err && + grep "pack-objects died" output.err ' test_expect_success 'create empty repository' ' diff --git a/t/t5706-clone-branch.sh b/t/t5706-clone-branch.sh new file mode 100755 index 000000000..f3f9a7605 --- /dev/null +++ b/t/t5706-clone-branch.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +test_description='clone --branch option' +. ./test-lib.sh + +check_HEAD() { + echo refs/heads/"$1" >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +} + +check_file() { + echo "$1" >expect && + test_cmp expect file +} + +test_expect_success 'setup' ' + mkdir parent && + (cd parent && git init && + echo one >file && git add file && git commit -m one && + git checkout -b two && + echo two >file && git add file && git commit -m two && + git checkout master) +' + +test_expect_success 'vanilla clone chooses HEAD' ' + git clone parent clone && + (cd clone && + check_HEAD master && + check_file one + ) +' + +test_expect_success 'clone -b chooses specified branch' ' + git clone -b two parent clone-two && + (cd clone-two && + check_HEAD two && + check_file two + ) +' + +test_expect_success 'clone -b sets up tracking' ' + (cd clone-two && + echo origin >expect && + git config branch.two.remote >actual && + echo refs/heads/two >>expect && + git config branch.two.merge >>actual && + test_cmp expect actual + ) +' + +test_expect_success 'clone -b does not munge remotes/origin/HEAD' ' + (cd clone-two && + echo refs/remotes/origin/master >expect && + git symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'clone -b with bogus branch chooses HEAD' ' + git clone -b bogus parent clone-bogus && + (cd clone-bogus && + check_HEAD master && + check_file one + ) +' + +test_done diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 04e4b7c5c..0144d9e85 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -110,6 +110,18 @@ test_expect_success 'compute merge-base (all)' \ # Another set to demonstrate base between one commit and a merge # in the documentation. +# +# * C (MMC) * B (MMB) * A (MMA) +# * o * o * o +# * o * o * o +# * o * o * o +# * o | _______/ +# | |/ +# | * 1 (MM1) +# | _______/ +# |/ +# * root (MMR) + test_expect_success 'merge-base for octopus-step (setup)' ' test_tick && git commit --allow-empty -m root && git tag MMR && @@ -137,6 +149,12 @@ test_expect_success 'merge-base A B C' ' test "$MM1" = "$MB" ' +test_expect_success 'merge-base A B C using show-branch' ' + MB=$(git show-branch --merge-base MMA MMB MMC) && + MMR=$(git rev-parse --verify MMR) && + test "$MMR" = "$MB" +' + test_expect_success 'criss-cross merge-base for octopus-step (setup)' ' git reset --hard MMR && test_tick && git commit --allow-empty -m 1 && git tag CC1 && diff --git a/t/t6015-rev-list-show-all-parents.sh b/t/t6015-rev-list-show-all-parents.sh new file mode 100755 index 000000000..8b146fb43 --- /dev/null +++ b/t/t6015-rev-list-show-all-parents.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='--show-all --parents does not rewrite TREESAME commits' + +. ./test-lib.sh + +test_expect_success 'set up --show-all --parents test' ' + test_commit one foo.txt && + commit1=`git rev-list -1 HEAD` && + test_commit two bar.txt && + commit2=`git rev-list -1 HEAD` && + test_commit three foo.txt && + commit3=`git rev-list -1 HEAD` + ' + +test_expect_success '--parents rewrites TREESAME parents correctly' ' + echo $commit3 $commit1 > expected && + echo $commit1 >> expected && + git rev-list --parents HEAD -- foo.txt > actual && + test_cmp expected actual + ' + +test_expect_success '--parents --show-all does not rewrites TREESAME parents' ' + echo $commit3 $commit2 > expected && + echo $commit2 $commit1 >> expected && + echo $commit1 >> expected && + git rev-list --parents --show-all HEAD -- foo.txt > actual && + test_cmp expected actual + ' + +test_done diff --git a/t/t6016-rev-list-graph-simplify-history.sh b/t/t6016-rev-list-graph-simplify-history.sh new file mode 100755 index 000000000..27fd52b7b --- /dev/null +++ b/t/t6016-rev-list-graph-simplify-history.sh @@ -0,0 +1,276 @@ +#!/bin/sh + +# There's more than one "correct" way to represent the history graphically. +# These tests depend on the current behavior of the graphing code. If the +# graphing code is ever changed to draw the output differently, these tests +# cases will need to be updated to know about the new layout. + +test_description='--graph and simplified history' + +. ./test-lib.sh + +test_expect_success 'set up rev-list --graph test' ' + # 3 commits on branch A + test_commit A1 foo.txt && + test_commit A2 bar.txt && + test_commit A3 bar.txt && + git branch -m master A && + + # 2 commits on branch B, started from A1 + git checkout -b B A1 && + test_commit B1 foo.txt && + test_commit B2 abc.txt && + + # 2 commits on branch C, started from A2 + git checkout -b C A2 && + test_commit C1 xyz.txt && + test_commit C2 xyz.txt && + + # Octopus merge B and C into branch A + git checkout A && + git merge B C && + git tag A4 + + test_commit A5 bar.txt && + + # More commits on C, then merge C into A + git checkout C && + test_commit C3 foo.txt && + test_commit C4 bar.txt && + git checkout A && + git merge -s ours C && + git tag A6 + + test_commit A7 bar.txt && + + # Store commit names in variables for later use + A1=$(git rev-parse --verify A1) && + A2=$(git rev-parse --verify A2) && + A3=$(git rev-parse --verify A3) && + A4=$(git rev-parse --verify A4) && + A5=$(git rev-parse --verify A5) && + A6=$(git rev-parse --verify A6) && + A7=$(git rev-parse --verify A7) && + B1=$(git rev-parse --verify B1) && + B2=$(git rev-parse --verify B2) && + C1=$(git rev-parse --verify C1) && + C2=$(git rev-parse --verify C2) && + C3=$(git rev-parse --verify C3) && + C4=$(git rev-parse --verify C4) + ' + +test_expect_success '--graph --all' ' + rm -f expected && + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "| * $C3" >> expected && + echo "* | $A5" >> expected && + echo "| | " >> expected && + echo "| \\ " >> expected && + echo "*-. \\ $A4" >> expected && + echo "|\\ \\ \\ " >> expected && + echo "| | |/ " >> expected && + echo "| | * $C2" >> expected && + echo "| | * $C1" >> expected && + echo "| * | $B2" >> expected && + echo "| * | $B1" >> expected && + echo "* | | $A3" >> expected && + echo "| |/ " >> expected && + echo "|/| " >> expected && + echo "* | $A2" >> expected && + echo "|/ " >> expected && + echo "* $A1" >> expected && + git rev-list --graph --all > actual && + test_cmp expected actual + ' + +# Make sure the graph_is_interesting() code still realizes +# that undecorated merges are interesting, even with --simplify-by-decoration +test_expect_success '--graph --simplify-by-decoration' ' + rm -f expected && + git tag -d A4 + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "| * $C3" >> expected && + echo "* | $A5" >> expected && + echo "| | " >> expected && + echo "| \\ " >> expected && + echo "*-. \\ $A4" >> expected && + echo "|\\ \\ \\ " >> expected && + echo "| | |/ " >> expected && + echo "| | * $C2" >> expected && + echo "| | * $C1" >> expected && + echo "| * | $B2" >> expected && + echo "| * | $B1" >> expected && + echo "* | | $A3" >> expected && + echo "| |/ " >> expected && + echo "|/| " >> expected && + echo "* | $A2" >> expected && + echo "|/ " >> expected && + echo "* $A1" >> expected && + git rev-list --graph --all --simplify-by-decoration > actual && + test_cmp expected actual + ' + +# Get rid of all decorations on branch B, and graph with it simplified away +test_expect_success '--graph --simplify-by-decoration prune branch B' ' + rm -f expected && + git tag -d B2 + git tag -d B1 + git branch -d B + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "| * $C3" >> expected && + echo "* | $A5" >> expected && + echo "* | $A4" >> expected && + echo "|\\ \\ " >> expected && + echo "| |/ " >> expected && + echo "| * $C2" >> expected && + echo "| * $C1" >> expected && + echo "* | $A3" >> expected && + echo "|/ " >> expected && + echo "* $A2" >> expected && + echo "* $A1" >> expected && + git rev-list --graph --simplify-by-decoration --all > actual && + test_cmp expected actual + ' + +test_expect_success '--graph --full-history -- bar.txt' ' + rm -f expected && + git tag -d B2 + git tag -d B1 + git branch -d B + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "* | $A5" >> expected && + echo "* | $A4" >> expected && + echo "|\\ \\ " >> expected && + echo "| |/ " >> expected && + echo "* | $A3" >> expected && + echo "|/ " >> expected && + echo "* $A2" >> expected && + git rev-list --graph --full-history --all -- bar.txt > actual && + test_cmp expected actual + ' + +test_expect_success '--graph --full-history --simplify-merges -- bar.txt' ' + rm -f expected && + git tag -d B2 + git tag -d B1 + git branch -d B + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "* | $A5" >> expected && + echo "* | $A3" >> expected && + echo "|/ " >> expected && + echo "* $A2" >> expected && + git rev-list --graph --full-history --simplify-merges --all \ + -- bar.txt > actual && + test_cmp expected actual + ' + +test_expect_success '--graph -- bar.txt' ' + rm -f expected && + git tag -d B2 + git tag -d B1 + git branch -d B + echo "* $A7" >> expected && + echo "* $A5" >> expected && + echo "* $A3" >> expected && + echo "| * $C4" >> expected && + echo "|/ " >> expected && + echo "* $A2" >> expected && + git rev-list --graph --all -- bar.txt > actual && + test_cmp expected actual + ' + +test_expect_success '--graph --sparse -- bar.txt' ' + rm -f expected && + git tag -d B2 + git tag -d B1 + git branch -d B + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "* $A5" >> expected && + echo "* $A4" >> expected && + echo "* $A3" >> expected && + echo "| * $C4" >> expected && + echo "| * $C3" >> expected && + echo "| * $C2" >> expected && + echo "| * $C1" >> expected && + echo "|/ " >> expected && + echo "* $A2" >> expected && + echo "* $A1" >> expected && + git rev-list --graph --sparse --all -- bar.txt > actual && + test_cmp expected actual + ' + +test_expect_success '--graph ^C4' ' + rm -f expected && + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "* $A5" >> expected && + echo "* $A4" >> expected && + echo "|\\ " >> expected && + echo "| * $B2" >> expected && + echo "| * $B1" >> expected && + echo "* $A3" >> expected && + git rev-list --graph --all ^C4 > actual && + test_cmp expected actual + ' + +test_expect_success '--graph ^C3' ' + rm -f expected && + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "* $A5" >> expected && + echo "* $A4" >> expected && + echo "|\\ " >> expected && + echo "| * $B2" >> expected && + echo "| * $B1" >> expected && + echo "* $A3" >> expected && + git rev-list --graph --all ^C3 > actual && + test_cmp expected actual + ' + +# I don't think the ordering of the boundary commits is really +# that important, but this test depends on it. If the ordering ever changes +# in the code, we'll need to update this test. +test_expect_success '--graph --boundary ^C3' ' + rm -f expected && + echo "* $A7" >> expected && + echo "* $A6" >> expected && + echo "|\\ " >> expected && + echo "| * $C4" >> expected && + echo "* | $A5" >> expected && + echo "| | " >> expected && + echo "| \\ " >> expected && + echo "*-. \\ $A4" >> expected && + echo "|\\ \\ \\ " >> expected && + echo "| * | | $B2" >> expected && + echo "| * | | $B1" >> expected && + echo "* | | | $A3" >> expected && + echo "o | | | $A2" >> expected && + echo "|/ / / " >> expected && + echo "o | | $A1" >> expected && + echo " / / " >> expected && + echo "| o $C3" >> expected && + echo "|/ " >> expected && + echo "o $C2" >> expected && + git rev-list --graph --boundary --all ^C3 > actual && + test_cmp expected actual + ' + +test_done diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh index a19d49de2..e71c687f2 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -22,4 +22,27 @@ git commit -m "File: dir"' test_expect_code 1 'Merge with d/f conflicts' 'git merge "merge msg" B master' +test_expect_failure 'F/D conflict' ' + git reset --hard && + git checkout master && + rm .git/index && + + mkdir before && + echo FILE >before/one && + echo FILE >after && + git add . && + git commit -m first && + + rm -f after && + git mv before after && + git commit -m move && + + git checkout -b para HEAD^ && + echo COMPLETELY ANOTHER FILE >another && + git add . && + git commit -m para && + + git merge master +' + test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh new file mode 100755 index 000000000..8b8bd81c0 --- /dev/null +++ b/t/t6050-replace.sh @@ -0,0 +1,200 @@ +#!/bin/sh +# +# Copyright (c) 2008 Christian Couder +# +test_description='Tests replace refs functionality' + +exec </dev/null + +. ./test-lib.sh + +add_and_commit_file() +{ + _file="$1" + _msg="$2" + + git add $_file || return $? + test_tick || return $? + git commit --quiet -m "$_file: $_msg" +} + +HASH1= +HASH2= +HASH3= +HASH4= +HASH5= +HASH6= +HASH7= + +test_expect_success 'set up buggy branch' ' + echo "line 1" >> hello && + echo "line 2" >> hello && + echo "line 3" >> hello && + echo "line 4" >> hello && + add_and_commit_file hello "4 lines" && + HASH1=$(git rev-parse --verify HEAD) && + echo "line BUG" >> hello && + echo "line 6" >> hello && + echo "line 7" >> hello && + echo "line 8" >> hello && + add_and_commit_file hello "4 more lines with a BUG" && + HASH2=$(git rev-parse --verify HEAD) && + echo "line 9" >> hello && + echo "line 10" >> hello && + add_and_commit_file hello "2 more lines" && + HASH3=$(git rev-parse --verify HEAD) && + echo "line 11" >> hello && + add_and_commit_file hello "1 more line" && + HASH4=$(git rev-parse --verify HEAD) && + sed -e "s/BUG/5/" hello > hello.new && + mv hello.new hello && + add_and_commit_file hello "BUG fixed" && + HASH5=$(git rev-parse --verify HEAD) && + echo "line 12" >> hello && + echo "line 13" >> hello && + add_and_commit_file hello "2 more lines" && + HASH6=$(git rev-parse --verify HEAD) + echo "line 14" >> hello && + echo "line 15" >> hello && + echo "line 16" >> hello && + add_and_commit_file hello "again 3 more lines" && + HASH7=$(git rev-parse --verify HEAD) +' + +test_expect_success 'replace the author' ' + git cat-file commit $HASH2 | grep "author A U Thor" && + R=$(git cat-file commit $HASH2 | sed -e "s/A U/O/" | git hash-object -t commit --stdin -w) && + git cat-file commit $R | grep "author O Thor" && + git update-ref refs/replace/$HASH2 $R && + git show HEAD~5 | grep "O Thor" && + git show $HASH2 | grep "O Thor" +' + +cat >tag.sig <<EOF +object $HASH2 +type commit +tag mytag +tagger T A Gger <> 0 +0000 + +EOF + +test_expect_success 'tag replaced commit' ' + git mktag <tag.sig >.git/refs/tags/mytag 2>message +' + +test_expect_success '"git fsck" works' ' + git fsck master > fsck_master.out && + grep "dangling commit $R" fsck_master.out && + grep "dangling tag $(cat .git/refs/tags/mytag)" fsck_master.out && + test -z "$(git fsck)" +' + +test_expect_success 'repack, clone and fetch work' ' + git repack -a -d && + git clone --no-hardlinks . clone_dir && + cd clone_dir && + git show HEAD~5 | grep "A U Thor" && + git show $HASH2 | grep "A U Thor" && + git cat-file commit $R && + git repack -a -d && + test_must_fail git cat-file commit $R && + git fetch ../ "refs/replace/*:refs/replace/*" && + git show HEAD~5 | grep "O Thor" && + git show $HASH2 | grep "O Thor" && + git cat-file commit $R && + cd .. +' + +test_expect_success '"git replace" listing and deleting' ' + test "$HASH2" = "$(git replace -l)" && + test "$HASH2" = "$(git replace)" && + aa=${HASH2%??????????????????????????????????????} && + test "$HASH2" = "$(git replace -l "$aa*")" && + test_must_fail git replace -d $R && + test_must_fail git replace -d && + test_must_fail git replace -l -d $HASH2 && + git replace -d $HASH2 && + git show $HASH2 | grep "A U Thor" && + test -z "$(git replace -l)" +' + +test_expect_success '"git replace" replacing' ' + git replace $HASH2 $R && + git show $HASH2 | grep "O Thor" && + test_must_fail git replace $HASH2 $R && + git replace -f $HASH2 $R && + test_must_fail git replace -f && + test "$HASH2" = "$(git replace)" +' + +# This creates a side branch where the bug in H2 +# does not appear because P2 is created by applying +# H2 and squashing H5 into it. +# P3, P4 and P6 are created by cherry-picking H3, H4 +# and H6 respectively. +# +# At this point, we should have the following: +# +# P2--P3--P4--P6 +# / +# H1-H2-H3-H4-H5-H6-H7 +# +# Then we replace H6 with P6. +# +test_expect_success 'create parallel branch without the bug' ' + git replace -d $HASH2 && + git show $HASH2 | grep "A U Thor" && + git checkout $HASH1 && + git cherry-pick $HASH2 && + git show $HASH5 | git apply && + git commit --amend -m "hello: 4 more lines WITHOUT the bug" hello && + PARA2=$(git rev-parse --verify HEAD) && + git cherry-pick $HASH3 && + PARA3=$(git rev-parse --verify HEAD) && + git cherry-pick $HASH4 && + PARA4=$(git rev-parse --verify HEAD) && + git cherry-pick $HASH6 && + PARA6=$(git rev-parse --verify HEAD) && + git replace $HASH6 $PARA6 && + git checkout master && + cur=$(git rev-parse --verify HEAD) && + test "$cur" = "$HASH7" && + git log --pretty=oneline | grep $PARA2 && + git remote add cloned ./clone_dir +' + +test_expect_success 'push to cloned repo' ' + git push cloned $HASH6^:refs/heads/parallel && + cd clone_dir && + git checkout parallel && + git log --pretty=oneline | grep $PARA2 && + cd .. +' + +test_expect_success 'push branch with replacement' ' + git cat-file commit $PARA3 | grep "author A U Thor" && + S=$(git cat-file commit $PARA3 | sed -e "s/A U/O/" | git hash-object -t commit --stdin -w) && + git cat-file commit $S | grep "author O Thor" && + git replace $PARA3 $S && + git show $HASH6~2 | grep "O Thor" && + git show $PARA3 | grep "O Thor" && + git push cloned $HASH6^:refs/heads/parallel2 && + cd clone_dir && + git checkout parallel2 && + git log --pretty=oneline | grep $PARA3 && + git show $PARA3 | grep "A U Thor" && + cd .. +' + +test_expect_success 'fetch branch with replacement' ' + git branch tofetch $HASH6 && + cd clone_dir && + git fetch origin refs/heads/tofetch:refs/heads/parallel3 + git log --pretty=oneline parallel3 | grep $PARA3 + git show $PARA3 | grep "A U Thor" + cd .. +' + +# +# +test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 6ca11d714..ae56a36ea 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -25,13 +25,17 @@ test_expect_success setup ' echo foo mmap bar_mmap echo foo_mmap bar mmap baz } >file && + echo vvv >v && echo ww w >w && echo x x xx x >x && echo y yy >y && echo zzz > z && mkdir t && echo test >t/t && - git add file w x y z t/t hello.c && + echo vvv >t/v && + mkdir t/a && + echo vvv >t/a/v && + git add . && test_tick && git commit -m initial ' @@ -132,6 +136,51 @@ do ! git grep -c test $H | grep /dev/null ' + test_expect_success "grep --max-depth -1 $L" ' + { + echo ${HC}t/a/v:1:vvv + echo ${HC}t/v:1:vvv + echo ${HC}v:1:vvv + } >expected && + git grep --max-depth -1 -n -e vvv $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep --max-depth 0 $L" ' + { + echo ${HC}v:1:vvv + } >expected && + git grep --max-depth 0 -n -e vvv $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep --max-depth 0 -- '*' $L" ' + { + echo ${HC}t/a/v:1:vvv + echo ${HC}t/v:1:vvv + echo ${HC}v:1:vvv + } >expected && + git grep --max-depth 0 -n -e vvv $H -- "*" >actual && + test_cmp expected actual + ' + + test_expect_success "grep --max-depth 1 $L" ' + { + echo ${HC}t/v:1:vvv + echo ${HC}v:1:vvv + } >expected && + git grep --max-depth 1 -n -e vvv $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep --max-depth 0 -- t $L" ' + { + echo ${HC}t/v:1:vvv + } >expected && + git grep --max-depth 0 -n -e vvv $H -- t >actual && + test_cmp expected actual + ' + done cat >expected <<EOF diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh new file mode 100755 index 000000000..1044aa654 --- /dev/null +++ b/t/t7060-wtstatus.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='basic work tree status reporting' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B oneside added && + git checkout A^0 && + test_commit C oneside created +' + +test_expect_success 'A/A conflict' ' + git checkout B^0 && + test_must_fail git merge C +' + +test_expect_success 'Report path with conflict' ' + git diff --cached --name-status >actual && + echo "U oneside" >expect && + test_cmp expect actual +' + +test_expect_success 'Report new path with conflict' ' + git diff --cached --name-status HEAD^ >actual && + echo "U oneside" >expect && + test_cmp expect actual +' + +cat >expect <<EOF +# On branch side +# Unmerged paths: +# (use "git reset HEAD <file>..." to unstage) +# (use "git add <file>..." to mark resolution) +# +# deleted by us: foo +# +no changes added to commit (use "git add" and/or "git commit -a") +EOF + +test_expect_success 'M/D conflict does not segfault' ' + mkdir mdconflict && + ( + cd mdconflict && + git init && + test_commit initial foo "" && + test_commit modify foo foo && + git checkout -b side HEAD^ && + git rm foo && + git commit -m delete && + test_must_fail git merge master && + test_must_fail git status > ../actual + ) && + test_cmp expect actual +' + +test_done diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index e637c7d4d..e85ff02c3 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -419,7 +419,8 @@ test_expect_success 'resetting an unmodified path is a no-op' ' ' cat > expect << EOF -file2: locally modified +Unstaged changes after reset: +M file2 EOF test_expect_success '--mixed refreshes the index' ' diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh new file mode 100755 index 000000000..c1f4fc3c6 --- /dev/null +++ b/t/t7105-reset-patch.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='git reset --patch' +. ./lib-patch-mode.sh + +test_expect_success 'setup' ' + mkdir dir && + echo parent > dir/foo && + echo dummy > bar && + git add dir && + git commit -m initial && + test_tick && + test_commit second dir/foo head && + set_and_save_state bar bar_work bar_index && + save_head +' + +# note: bar sorts before foo, so the first 'n' is always to skip 'bar' + +test_expect_success 'saying "n" does nothing' ' + set_and_save_state dir/foo work work + (echo n; echo n) | git reset -p && + verify_saved_state dir/foo && + verify_saved_state bar +' + +test_expect_success 'git reset -p' ' + (echo n; echo y) | git reset -p && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^' ' + (echo n; echo y) | git reset -p HEAD^ && + verify_state dir/foo work parent && + verify_saved_state bar +' + +# The idea in the rest is that bar sorts first, so we always say 'y' +# first and if the path limiter fails it'll apply to bar instead of +# dir/foo. There's always an extra 'n' to reject edits to dir/foo in +# the failure case (and thus get out of the loop). + +test_expect_success 'git reset -p dir' ' + set_state dir/foo work work + (echo y; echo n) | git reset -p dir && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p -- foo (inside dir)' ' + set_state dir/foo work work + (echo y; echo n) | (cd dir && git reset -p -- foo) && + verify_state dir/foo work head && + verify_saved_state bar +' + +test_expect_success 'git reset -p HEAD^ -- dir' ' + (echo y; echo n) | git reset -p HEAD^ -- dir && + verify_state dir/foo work parent && + verify_saved_state bar +' + +test_expect_success 'none of this moved HEAD' ' + verify_saved_head +' + + +test_done diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 61498293b..6cc16c39f 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -56,6 +56,15 @@ test_expect_success 'modified submodule(forward)' " EOF " +test_expect_success 'modified submodule(forward), --files' " + git submodule summary --files >actual && + diff actual - <<-EOF +* sm1 $head1...$head2 (1): + > Add foo3 + +EOF +" + commit_file sm1 && cd sm1 && git reset --hard HEAD~2 >/dev/null && @@ -114,6 +123,15 @@ test_expect_success 'typechanged submodule(submodule->blob), --cached' " EOF " +test_expect_success 'typechanged submodule(submodule->blob), --files' " + git submodule summary --files >actual && + diff actual - <<-EOF +* sm1 $head5(blob)->$head4(submodule) (3): + > Add foo5 + +EOF +" + rm -rf sm1 && git checkout-index sm1 test_expect_success 'typechanged submodule(submodule->blob)' " @@ -205,4 +223,8 @@ test_expect_success '--for-status' " EOF " +test_expect_success 'fail when using --files together with --cached' " + test_must_fail git submodule summary --files --cached +" + test_done diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh new file mode 100755 index 000000000..2a527750c --- /dev/null +++ b/t/t7407-submodule-foreach.sh @@ -0,0 +1,237 @@ +#!/bin/sh +# +# Copyright (c) 2009 Johan Herland +# + +test_description='Test "git submodule foreach" + +This test verifies that "git submodule foreach" correctly visits all submodules +that are currently checked out. +' + +. ./test-lib.sh + + +test_expect_success 'setup a submodule tree' ' + echo file > file && + git add file && + test_tick && + git commit -m upstream + git clone . super && + git clone super submodule && + ( + cd super && + git submodule add ../submodule sub1 && + git submodule add ../submodule sub2 && + git submodule add ../submodule sub3 && + git config -f .gitmodules --rename-section \ + submodule.sub1 submodule.foo1 && + git config -f .gitmodules --rename-section \ + submodule.sub2 submodule.foo2 && + git config -f .gitmodules --rename-section \ + submodule.sub3 submodule.foo3 && + git add .gitmodules + test_tick && + git commit -m "submodules" && + git submodule init sub1 && + git submodule init sub2 && + git submodule init sub3 + ) && + ( + cd submodule && + echo different > file && + git add file && + test_tick && + git commit -m "different" + ) && + ( + cd super && + ( + cd sub3 && + git pull + ) && + git add sub3 && + test_tick && + git commit -m "update sub3" + ) +' + +sub1sha1=$(cd super/sub1 && git rev-parse HEAD) +sub3sha1=$(cd super/sub3 && git rev-parse HEAD) + +cat > expect <<EOF +Entering 'sub1' +foo1-sub1-$sub1sha1 +Entering 'sub3' +foo3-sub3-$sub3sha1 +EOF + +test_expect_success 'test basic "submodule foreach" usage' ' + git clone super clone && + ( + cd clone && + git submodule update --init -- sub1 sub3 && + git submodule foreach "echo \$name-\$path-\$sha1" > ../actual + ) && + test_cmp expect actual +' + +test_expect_success 'setup nested submodules' ' + git clone submodule nested1 && + git clone submodule nested2 && + git clone submodule nested3 && + ( + cd nested3 && + git submodule add ../submodule submodule && + test_tick && + git commit -m "submodule" && + git submodule init submodule + ) && + ( + cd nested2 && + git submodule add ../nested3 nested3 && + test_tick && + git commit -m "nested3" && + git submodule init nested3 + ) && + ( + cd nested1 && + git submodule add ../nested2 nested2 && + test_tick && + git commit -m "nested2" && + git submodule init nested2 + ) && + ( + cd super && + git submodule add ../nested1 nested1 && + test_tick && + git commit -m "nested1" && + git submodule init nested1 + ) +' + +test_expect_success 'use "submodule foreach" to checkout 2nd level submodule' ' + git clone super clone2 && + ( + cd clone2 && + test ! -d sub1/.git && + test ! -d sub2/.git && + test ! -d sub3/.git && + test ! -d nested1/.git && + git submodule update --init && + test -d sub1/.git && + test -d sub2/.git && + test -d sub3/.git && + test -d nested1/.git && + test ! -d nested1/nested2/.git && + git submodule foreach "git submodule update --init" && + test -d nested1/nested2/.git && + test ! -d nested1/nested2/nested3/.git + ) +' + +test_expect_success 'use "foreach --recursive" to checkout all submodules' ' + ( + cd clone2 && + git submodule foreach --recursive "git submodule update --init" && + test -d nested1/nested2/nested3/.git && + test -d nested1/nested2/nested3/submodule/.git + ) +' + +cat > expect <<EOF +Entering 'nested1' +Entering 'nested1/nested2' +Entering 'nested1/nested2/nested3' +Entering 'nested1/nested2/nested3/submodule' +Entering 'sub1' +Entering 'sub2' +Entering 'sub3' +EOF + +test_expect_success 'test messages from "foreach --recursive"' ' + ( + cd clone2 && + git submodule foreach --recursive "true" > ../actual + ) && + test_cmp expect actual +' + +cat > expect <<EOF +nested1-nested1 +nested2-nested2 +nested3-nested3 +submodule-submodule +foo1-sub1 +foo2-sub2 +foo3-sub3 +EOF + +test_expect_success 'test "foreach --quiet --recursive"' ' + ( + cd clone2 && + git submodule foreach -q --recursive "echo \$name-\$path" > ../actual + ) && + test_cmp expect actual +' + +test_expect_success 'use "update --recursive" to checkout all submodules' ' + git clone super clone3 && + ( + cd clone3 && + test ! -d sub1/.git && + test ! -d sub2/.git && + test ! -d sub3/.git && + test ! -d nested1/.git && + git submodule update --init --recursive && + test -d sub1/.git && + test -d sub2/.git && + test -d sub3/.git && + test -d nested1/.git && + test -d nested1/nested2/.git && + test -d nested1/nested2/nested3/.git && + test -d nested1/nested2/nested3/submodule/.git + ) +' + +nested1sha1=$(cd clone3/nested1 && git rev-parse HEAD) +nested2sha1=$(cd clone3/nested1/nested2 && git rev-parse HEAD) +nested3sha1=$(cd clone3/nested1/nested2/nested3 && git rev-parse HEAD) +submodulesha1=$(cd clone3/nested1/nested2/nested3/submodule && git rev-parse HEAD) +sub1sha1=$(cd clone3/sub1 && git rev-parse HEAD) +sub2sha1=$(cd clone3/sub2 && git rev-parse HEAD) +sub3sha1=$(cd clone3/sub3 && git rev-parse HEAD) +sub1sha1_short=$(cd clone3/sub1 && git rev-parse --short HEAD) +sub2sha1_short=$(cd clone3/sub2 && git rev-parse --short HEAD) + +cat > expect <<EOF + $nested1sha1 nested1 (heads/master) + $nested2sha1 nested1/nested2 (heads/master) + $nested3sha1 nested1/nested2/nested3 (heads/master) + $submodulesha1 nested1/nested2/nested3/submodule (heads/master) + $sub1sha1 sub1 ($sub1sha1_short) + $sub2sha1 sub2 ($sub2sha1_short) + $sub3sha1 sub3 (heads/master) +EOF + +test_expect_success 'test "status --recursive"' ' + ( + cd clone3 && + git submodule status --recursive > ../actual + ) && + test_cmp expect actual +' + +test_expect_success 'use "git clone --recursive" to checkout all submodules' ' + git clone --recursive super clone4 && + test -d clone4/.git && + test -d clone4/sub1/.git && + test -d clone4/sub2/.git && + test -d clone4/sub3/.git && + test -d clone4/nested1/.git && + test -d clone4/nested1/nested2/.git && + test -d clone4/nested1/nested2/nested3/.git && + test -d clone4/nested1/nested2/nested3/submodule/.git +' + +test_done diff --git a/t/t7406-submodule-reference.sh b/t/t7408-submodule-reference.sh index cc16d3f05..cc16d3f05 100755 --- a/t/t7406-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 9da4178c9..929499e99 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -142,7 +142,9 @@ test_expect_success 'test show-ignore' " touch deeply/nested/directory/.keep && svn_cmd add deeply && svn_cmd up && - svn_cmd propset -R svn:ignore 'no-such-file*' . + svn_cmd propset -R svn:ignore ' +no-such-file* +' . svn_cmd commit -m 'propset svn:ignore' cd .. && git svn show-ignore > show-ignore.got && @@ -171,6 +173,7 @@ test_expect_success 'test create-ignore' " " cat >prop.expect <<\EOF + no-such-file* EOF diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 78610b61e..bbfd7f479 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -172,11 +172,11 @@ test_expect_success "follow-parent is atomic" ' git update-ref refs/remotes/flunk@18 refs/remotes/stunk~2 && git update-ref -d refs/remotes/stunk && git config --unset svn-remote.svn.fetch stunk && - mkdir -p "$GIT_DIR"/svn/flunk@18 && - rev_map=$(cd "$GIT_DIR"/svn/stunk && ls .rev_map*) && - dd if="$GIT_DIR"/svn/stunk/$rev_map \ - of="$GIT_DIR"/svn/flunk@18/$rev_map bs=24 count=1 && - rm -rf "$GIT_DIR"/svn/stunk && + mkdir -p "$GIT_DIR"/svn/refs/remotes/flunk@18 && + rev_map=$(cd "$GIT_DIR"/svn/refs/remotes/stunk && ls .rev_map*) && + dd if="$GIT_DIR"/svn/refs/remotes/stunk/$rev_map \ + of="$GIT_DIR"/svn/refs/remotes/flunk@18/$rev_map bs=24 count=1 && + rm -rf "$GIT_DIR"/svn/refs/remotes/stunk && git svn init --minimize-url -i flunk "$svnrepo"/flunk && git svn fetch -i flunk && git svn init --minimize-url -i stunk "$svnrepo"/stunk && diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index 3a9e07768..901b8e09f 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -16,9 +16,7 @@ test_expect_success 'setup old-looking metadata' ' cd .. && git svn init "$svnrepo" && git svn fetch && - mv "$GIT_DIR"/svn/* "$GIT_DIR"/ && - mv "$GIT_DIR"/svn/.metadata "$GIT_DIR"/ && - rmdir "$GIT_DIR"/svn && + rm -rf "$GIT_DIR"/svn && git update-ref refs/heads/git-svn-HEAD refs/${remotes_git_svn} && git update-ref refs/heads/svn-HEAD refs/${remotes_git_svn} && git update-ref -d refs/${remotes_git_svn} refs/${remotes_git_svn} @@ -56,7 +54,15 @@ test_expect_success 'initialize a multi-repository repo' ' git config --add svn-remote.svn.fetch "branches/b:refs/remotes/b" && for i in tags/0.1 tags/0.2 tags/0.3; do git config --add svn-remote.svn.fetch \ - $i:refs/remotes/$i || exit 1; done + $i:refs/remotes/$i || exit 1; done && + git config --get-all svn-remote.svn.fetch > fetch.out && + grep "^trunk:refs/remotes/trunk$" fetch.out && + grep "^branches/a:refs/remotes/a$" fetch.out && + grep "^branches/b:refs/remotes/b$" fetch.out && + grep "^tags/0\.1:refs/remotes/tags/0\.1$" fetch.out && + grep "^tags/0\.2:refs/remotes/tags/0\.2$" fetch.out && + grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out && + grep "^:refs/${remotes_git_svn}" fetch.out ' # refs should all be different, but the trees should all be the same: @@ -79,36 +85,36 @@ test_expect_success 'migrate --minimize on old inited layout' ' rm -rf "$GIT_DIR"/svn && for i in `cat fetch.out`; do path=`expr $i : "\([^:]*\):.*$"` - ref=`expr $i : "[^:]*:refs/remotes/\(.*\)$"` + ref=`expr $i : "[^:]*:\(refs/remotes/.*\)$"` if test -z "$ref"; then continue; fi if test -n "$path"; then path="/$path"; fi ( mkdir -p "$GIT_DIR"/svn/$ref/info/ && echo "$svnrepo"$path > "$GIT_DIR"/svn/$ref/info/url ) || exit 1; done && git svn migrate --minimize && - test -z "`git config -l |grep -v "^svn-remote\.git-svn\."`" && + test -z "`git config -l | grep "^svn-remote\.git-svn\."`" && git config --get-all svn-remote.svn.fetch > fetch.out && grep "^trunk:refs/remotes/trunk$" fetch.out && grep "^branches/a:refs/remotes/a$" fetch.out && grep "^branches/b:refs/remotes/b$" fetch.out && grep "^tags/0\.1:refs/remotes/tags/0\.1$" fetch.out && grep "^tags/0\.2:refs/remotes/tags/0\.2$" fetch.out && - grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out + grep "^tags/0\.3:refs/remotes/tags/0\.3$" fetch.out && grep "^:refs/${remotes_git_svn}" fetch.out ' test_expect_success ".rev_db auto-converted to .rev_map.UUID" ' git svn fetch -i trunk && - test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" && - expect="$(ls "$GIT_DIR"/svn/trunk/.rev_map.*)" && + test -z "$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db.* 2>/dev/null)" && + expect="$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_map.*)" && test -n "$expect" && rev_db="$(echo $expect | sed -e "s,_map,_db,")" && convert_to_rev_db "$expect" "$rev_db" && rm -f "$expect" && test -f "$rev_db" && git svn fetch -i trunk && - test -z "$(ls "$GIT_DIR"/svn/trunk/.rev_db.* 2>/dev/null)" && - test ! -e "$GIT_DIR"/svn/trunk/.rev_db && + test -z "$(ls "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db.* 2>/dev/null)" && + test ! -e "$GIT_DIR"/svn/refs/remotes/trunk/.rev_db && test -f "$expect" ' diff --git a/t/t9120-git-svn-clone-with-percent-escapes.sh b/t/t9120-git-svn-clone-with-percent-escapes.sh index f159ab689..9d9ebd533 100755 --- a/t/t9120-git-svn-clone-with-percent-escapes.sh +++ b/t/t9120-git-svn-clone-with-percent-escapes.sh @@ -10,6 +10,10 @@ test_expect_success 'setup svnrepo' ' mkdir project project/trunk project/branches project/tags && echo foo > project/trunk/foo && svn_cmd import -m "$test_description" project "$svnrepo/pr ject" && + svn_cmd cp -m "branch" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/branches/b" && + svn_cmd cp -m "tag" "$svnrepo/pr ject/trunk" \ + "$svnrepo/pr ject/tags/v1" && rm -rf project && start_httpd ' @@ -21,6 +25,54 @@ test_expect_success 'test clone with percent escapes' ' cd .. ' +# SVN works either way, so should we... + +test_expect_success 'svn checkout with percent escapes' ' + svn_cmd checkout "$svnrepo/pr%20ject" svn.percent && + svn_cmd checkout "$svnrepo/pr%20ject/trunk" svn.percent.trunk +' + +test_expect_success 'svn checkout with space' ' + svn_cmd checkout "$svnrepo/pr ject" svn.space && + svn_cmd checkout "$svnrepo/pr ject/trunk" svn.space.trunk +' + +test_expect_success 'test clone trunk with percent escapes and minimize-url' ' + git svn clone --minimize-url "$svnrepo/pr%20ject/trunk" minimize && + ( + cd minimize && + git rev-parse refs/${remotes_git_svn} + ) +' + +test_expect_success 'test clone trunk with percent escapes' ' + git svn clone "$svnrepo/pr%20ject/trunk" trunk && + ( + cd trunk && + git rev-parse refs/${remotes_git_svn} + ) +' + +test_expect_success 'test clone --stdlayout with percent escapes' ' + git svn clone --stdlayout "$svnrepo/pr%20ject" percent && + ( + cd percent && + git rev-parse refs/remotes/trunk^0 && + git rev-parse refs/remotes/b^0 && + git rev-parse refs/remotes/tags/v1^0 + ) +' + +test_expect_success 'test clone -s with unescaped space' ' + git svn clone -s "$svnrepo/pr ject" space && + ( + cd space && + git rev-parse refs/remotes/trunk^0 && + git rev-parse refs/remotes/b^0 && + git rev-parse refs/remotes/tags/v1^0 + ) +' + stop_httpd test_done diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh index 03705fa4c..5280e5f1e 100755 --- a/t/t9135-git-svn-moved-branch-empty-file.sh +++ b/t/t9135-git-svn-moved-branch-empty-file.sh @@ -10,7 +10,12 @@ test_expect_success 'load svn dumpfile' ' test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x' test_expect_success 'test that b1 exists and is empty' ' - (cd x && test -f b1 && ! test -s b1) + ( + cd x && + git reset --hard branch-c && + test -f b1 && + ! test -s b1 + ) ' test_done diff --git a/t/t9143-git-svn-gc.sh b/t/t9143-git-svn-gc.sh index f2ba2d1da..99f69c6a0 100755 --- a/t/t9143-git-svn-gc.sh +++ b/t/t9143-git-svn-gc.sh @@ -28,26 +28,26 @@ test_expect_success 'Setup repo' 'git svn init "$svnrepo"' test_expect_success 'Fetch repo' 'git svn fetch' test_expect_success 'make backup copy of unhandled.log' ' - cp .git/svn/git-svn/unhandled.log tmp + cp .git/svn/refs/remotes/git-svn/unhandled.log tmp ' -test_expect_success 'create leftover index' '> .git/svn/git-svn/index' +test_expect_success 'create leftover index' '> .git/svn/refs/remotes/git-svn/index' test_expect_success 'git svn gc runs' 'git svn gc' -test_expect_success 'git svn index removed' '! test -f .git/svn/git-svn/index' +test_expect_success 'git svn index removed' '! test -f .git/svn/refs/remotes/git-svn/index' if perl -MCompress::Zlib -e 0 2>/dev/null then test_expect_success 'git svn gc produces a valid gzip file' ' - gunzip .git/svn/git-svn/unhandled.log.gz + gunzip .git/svn/refs/remotes/git-svn/unhandled.log.gz ' else say "Perl Compress::Zlib unavailable, skipping gunzip test" fi test_expect_success 'git svn gc does not change unhandled.log files' ' - test_cmp .git/svn/git-svn/unhandled.log tmp/unhandled.log + test_cmp .git/svn/refs/remotes/git-svn/unhandled.log tmp/unhandled.log ' test_done diff --git a/t/t9144-git-svn-old-rev_map.sh b/t/t9144-git-svn-old-rev_map.sh new file mode 100755 index 000000000..7600a35cd --- /dev/null +++ b/t/t9144-git-svn-old-rev_map.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# Copyright (c) 2009 Eric Wong + +test_description='git svn old rev_map preservd' +. ./lib-git-svn.sh + +test_expect_success 'setup test repository with old layout' ' + mkdir i && + (cd i && > a) && + svn_cmd import -m- i "$svnrepo" && + git svn init "$svnrepo" && + git svn fetch && + test -d .git/svn/refs/remotes/git-svn/ && + ! test -e .git/svn/git-svn/ && + mv .git/svn/refs/remotes/git-svn .git/svn/ && + rm -r .git/svn/refs +' + +test_expect_success 'old layout continues to work' ' + svn_cmd import -m- i "$svnrepo/b" && + git svn rebase && + echo a >> b/a && + git add b/a && + git commit -m- -a && + git svn dcommit && + ! test -d .git/svn/refs/ && + test -e .git/svn/git-svn/ +' + +test_done diff --git a/t/t9145-git-svn-master-branch.sh b/t/t9145-git-svn-master-branch.sh new file mode 100755 index 000000000..16852d26a --- /dev/null +++ b/t/t9145-git-svn-master-branch.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Copyright (c) 2009 Eric Wong +# +test_description='git svn initial master branch is "trunk" if possible' +. ./lib-git-svn.sh + +test_expect_success 'setup test repository' ' + mkdir i && + > i/a && + svn_cmd import -m trunk i "$svnrepo/trunk" && + svn_cmd import -m b/a i "$svnrepo/branches/a" && + svn_cmd import -m b/b i "$svnrepo/branches/b" +' + +test_expect_success 'git svn clone --stdlayout sets up trunk as master' ' + git svn clone -s "$svnrepo" g && + ( + cd g && + test x`git rev-parse --verify refs/remotes/trunk^0` = \ + x`git rev-parse --verify refs/heads/master^0` + ) +' + +test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 627518108..2fc7fdb12 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -9,73 +9,8 @@ This test runs gitweb (git web interface) as CGI script from commandline, and checks that it would not write any errors or warnings to log.' -gitweb_init () { - safe_pwd="$(perl -MPOSIX=getcwd -e 'print quotemeta(getcwd)')" - cat >gitweb_config.perl <<EOF -#!/usr/bin/perl - -# gitweb configuration for tests - -our \$version = "current"; -our \$GIT = "git"; -our \$projectroot = "$safe_pwd"; -our \$project_maxdepth = 8; -our \$home_link_str = "projects"; -our \$site_name = "[localhost]"; -our \$site_header = ""; -our \$site_footer = ""; -our \$home_text = "indextext.html"; -our @stylesheets = ("file:///$TEST_DIRECTORY/../gitweb/gitweb.css"); -our \$logo = "file:///$TEST_DIRECTORY/../gitweb/git-logo.png"; -our \$favicon = "file:///$TEST_DIRECTORY/../gitweb/git-favicon.png"; -our \$projects_list = ""; -our \$export_ok = ""; -our \$strict_export = ""; -EOF - - cat >.git/description <<EOF -$0 test repository -EOF -} - -gitweb_run () { - GATEWAY_INTERFACE="CGI/1.1" - HTTP_ACCEPT="*/*" - REQUEST_METHOD="GET" - SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl" - QUERY_STRING=""$1"" - PATH_INFO=""$2"" - export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \ - SCRIPT_NAME QUERY_STRING PATH_INFO - - GITWEB_CONFIG=$(pwd)/gitweb_config.perl - export GITWEB_CONFIG - - # some of git commands write to STDERR on error, but this is not - # written to web server logs, so we are not interested in that: - # we are interested only in properly formatted errors/warnings - rm -f gitweb.log && - perl -- "$SCRIPT_NAME" \ - >/dev/null 2>gitweb.log && - if grep "^[[]" gitweb.log >/dev/null 2>&1; then false; else true; fi - - # gitweb.log is left for debugging -} - -. ./test-lib.sh - -if ! test_have_prereq PERL; then - say 'skipping gitweb tests, perl not available' - test_done -fi - -perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { - say 'skipping gitweb tests, perl version is too old' - test_done -} - -gitweb_init +. ./gitweb-lib.sh # ---------------------------------------------------------------------- # no commits (empty, just initialized repository) diff --git a/t/t9501-gitweb-standalone-http-status.sh b/t/t9501-gitweb-standalone-http-status.sh new file mode 100644 index 000000000..d0ff21d42 --- /dev/null +++ b/t/t9501-gitweb-standalone-http-status.sh @@ -0,0 +1,78 @@ +#!/bin/sh +# +# Copyright (c) 2009 Mark Rada +# + +test_description='gitweb as standalone script (http status tests). + +This test runs gitweb (git web interface) as a CGI script from the +commandline, and checks that it returns the expected HTTP status +code and message.' + + +. ./gitweb-lib.sh + +# ---------------------------------------------------------------------- +# snapshot settings + +test_commit \ + 'SnapshotTests' \ + 'i can has snapshot?' + +cat >>gitweb_config.perl <<\EOF +$feature{'snapshot'}{'override'} = 0; +EOF + +test_expect_success \ + 'snapshots: tgz only default format enabled' \ + 'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" && + grep "Status: 200 OK" gitweb.output && + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tbz2" && + grep "403 - Unsupported snapshot format" gitweb.output && + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=txz" && + grep "403 - Snapshot format not allowed" gitweb.output && + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" && + grep "403 - Unsupported snapshot format" gitweb.output' +test_debug 'cat gitweb.output' + + +cat >>gitweb_config.perl <<\EOF +$feature{'snapshot'}{'default'} = ['tgz','tbz2','txz','zip']; +EOF + +test_expect_success \ + 'snapshots: all enabled in default, use default disabled value' \ + 'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" && + grep "Status: 200 OK" gitweb.output && + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tbz2" && + grep "Status: 200 OK" gitweb.output && + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=txz" && + grep "403 - Snapshot format not allowed" gitweb.output && + gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" && + grep "Status: 200 OK" gitweb.output' +test_debug 'cat gitweb.output' + + +cat >>gitweb_config.perl <<\EOF +$known_snapshot_formats{'zip'}{'disabled'} = 1; +EOF + +test_expect_success \ + 'snapshots: zip explicitly disabled' \ + 'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=zip" && + grep "403 - Snapshot format not allowed" gitweb.output' +test_debug 'cat gitweb.output' + + +cat >>gitweb_config.perl <<\EOF +$known_snapshot_formats{'tgz'}{'disabled'} = 0; +EOF + +test_expect_success \ + 'snapshots: tgz explicitly enabled' \ + 'gitweb_run "p=.git;a=snapshot;h=HEAD;sf=tgz" && + grep "Status: 200 OK" gitweb.output' +test_debug 'cat gitweb.output' + + +test_done diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 4322a0c1e..363345fae 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -1,7 +1,7 @@ #!/bin/sh test_description='git cvsimport basic tests' -. ./test-lib.sh +. ./lib-cvs.sh if ! test_have_prereq PERL; then say 'skipping git cvsimport tests, perl not available' @@ -10,37 +10,13 @@ fi CVSROOT=$(pwd)/cvsroot export CVSROOT -unset CVS_SERVER -# for clean cvsps cache -HOME=$(pwd) -export HOME - -if ! type cvs >/dev/null 2>&1 -then - say 'skipping cvsimport tests, cvs not found' - test_done -fi - -cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'` -case "$cvsps_version" in -2.1 | 2.2*) - ;; -'') - say 'skipping cvsimport tests, cvsps not found' - test_done - ;; -*) - say 'skipping cvsimport tests, unsupported cvsps version' - test_done - ;; -esac -test_expect_success 'setup cvsroot' 'cvs init' +test_expect_success 'setup cvsroot' '$CVS init' test_expect_success 'setup a cvs module' ' mkdir "$CVSROOT/module" && - cvs co -d module-cvs module && + $CVS co -d module-cvs module && cd module-cvs && cat <<EOF >o_fortuna && O Fortuna @@ -59,13 +35,13 @@ egestatem, potestatem dissolvit ut glaciem. EOF - cvs add o_fortuna && + $CVS add o_fortuna && cat <<EOF >message && add "O Fortuna" lyrics These public domain lyrics make an excellent sample text. EOF - cvs commit -F message && + $CVS commit -F message && cd .. ' @@ -103,7 +79,7 @@ translate to English My Latin is terrible. EOF - cvs commit -F message && + $CVS commit -F message && cd .. ' @@ -121,8 +97,8 @@ test_expect_success 'update cvs module' ' cd module-cvs && echo 1 >tick && - cvs add tick && - cvs commit -m 1 + $CVS add tick && + $CVS commit -m 1 cd .. ' @@ -140,7 +116,7 @@ test_expect_success 'cvsimport.module config works' ' test_expect_success 'import from a CVS working tree' ' - cvs co -d import-from-wt module && + $CVS co -d import-from-wt module && cd import-from-wt && git cvsimport -a -z0 && echo 1 >expect && @@ -150,4 +126,6 @@ test_expect_success 'import from a CVS working tree' ' ' +test_expect_success 'test entire HEAD' 'test_cmp_branch_tree master' + test_done diff --git a/t/t9601-cvsimport-vendor-branch.sh b/t/t9601-cvsimport-vendor-branch.sh new file mode 100755 index 000000000..3afaf5652 --- /dev/null +++ b/t/t9601-cvsimport-vendor-branch.sh @@ -0,0 +1,86 @@ +#!/bin/sh + +# Description of the files in the repository: +# +# imported-once.txt: +# +# Imported once. 1.1 and 1.1.1.1 should be identical. +# +# imported-twice.txt: +# +# Imported twice. HEAD should reflect the contents of the +# second import (i.e., have the same contents as 1.1.1.2). +# +# imported-modified.txt: +# +# Imported, then modified on HEAD. HEAD should reflect the +# modification. +# +# imported-modified-imported.txt: +# +# Imported, then modified on HEAD, then imported again. +# +# added-imported.txt,v: +# +# Added with 'cvs add' to create 1.1, then imported with +# completely different contents to create 1.1.1.1, therefore the +# vendor branch was never the default branch. +# +# imported-anonymously.txt: +# +# Like imported-twice.txt, but with a vendor branch whose branch +# tag has been removed. + +test_description='git cvsimport handling of vendor branches' +. ./lib-cvs.sh + +CVSROOT="$TEST_DIRECTORY"/t9601/cvsroot +export CVSROOT + +test_expect_success 'import a module with a vendor branch' ' + + git cvsimport -C module-git module + +' + +test_expect_success 'check HEAD out of cvs repository' 'test_cvs_co master' + +test_expect_success 'check master out of git repository' 'test_git_co master' + +test_expect_success 'check a file that was imported once' ' + + test_cmp_branch_file master imported-once.txt + +' + +test_expect_failure 'check a file that was imported twice' ' + + test_cmp_branch_file master imported-twice.txt + +' + +test_expect_success 'check a file that was imported then modified on HEAD' ' + + test_cmp_branch_file master imported-modified.txt + +' + +test_expect_success 'check a file that was imported, modified, then imported again' ' + + test_cmp_branch_file master imported-modified-imported.txt + +' + +test_expect_success 'check a file that was added to HEAD then imported' ' + + test_cmp_branch_file master added-imported.txt + +' + +test_expect_success 'a vendor branch whose tag has been removed' ' + + test_cmp_branch_file master imported-anonymously.txt + +' + +test_done diff --git a/t/t9601/cvsroot/.gitattributes b/t/t9601/cvsroot/.gitattributes new file mode 100644 index 000000000..562b12e16 --- /dev/null +++ b/t/t9601/cvsroot/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/t/t9601/cvsroot/CVSROOT/.gitignore b/t/t9601/cvsroot/CVSROOT/.gitignore new file mode 100644 index 000000000..3bb9b3417 --- /dev/null +++ b/t/t9601/cvsroot/CVSROOT/.gitignore @@ -0,0 +1,2 @@ +history +val-tags diff --git a/t/t9601/cvsroot/module/added-imported.txt,v b/t/t9601/cvsroot/module/added-imported.txt,v new file mode 100644 index 000000000..5f83072ea --- /dev/null +++ b/t/t9601/cvsroot/module/added-imported.txt,v @@ -0,0 +1,44 @@ +head 1.1; +access; +symbols + vtag-4:1.1.1.1 + vbranchA:1.1.1; +locks; strict; +comment @# @; + + +1.1 +date 2004.02.09.15.43.15; author kfogel; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2004.02.09.15.43.16; author kfogel; state Exp; +branches; +next ; + + +desc +@@ + + +1.1 +log +@Add a file to the working copy. +@ +text +@Adding this file, before importing it with different contents. +@ + + +1.1.1.1 +log +@Import (vbranchA, vtag-4). +@ +text +@d1 1 +a1 1 +This is vtag-4 (on vbranchA) of added-then-imported.txt. +@ + diff --git a/t/t9601/cvsroot/module/imported-anonymously.txt,v b/t/t9601/cvsroot/module/imported-anonymously.txt,v new file mode 100644 index 000000000..55e1b0ca5 --- /dev/null +++ b/t/t9601/cvsroot/module/imported-anonymously.txt,v @@ -0,0 +1,42 @@ +head 1.1; +branch 1.1.1; +access; +symbols + vtag-1:1.1.1.1; +locks; strict; +comment @# @; + + +1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next ; + + +desc +@@ + + +1.1 +log +@Initial revision +@ +text +@This is vtag-1 (on vbranchA) of imported-anonymously.txt. +@ + + +1.1.1.1 +log +@Import (vbranchA, vtag-1). +@ +text +@@ + + diff --git a/t/t9601/cvsroot/module/imported-modified-imported.txt,v b/t/t9601/cvsroot/module/imported-modified-imported.txt,v new file mode 100644 index 000000000..e5830aeb3 --- /dev/null +++ b/t/t9601/cvsroot/module/imported-modified-imported.txt,v @@ -0,0 +1,76 @@ +head 1.2; +access; +symbols + vtag-2:1.1.1.2 + vtag-1:1.1.1.1 + vbranchA:1.1.1; +locks; strict; +comment @# @; + + +1.2 +date 2004.02.09.15.43.14; author kfogel; state Exp; +branches; +next 1.1; + +1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next 1.1.1.2; + +1.1.1.2 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next ; + + +desc +@@ + + +1.2 +log +@First regular commit, to imported-modified-imported.txt, on HEAD. +@ +text +@This is a modification of imported-modified-imported.txt on HEAD. +It should supersede the version from the vendor branch. +@ + + +1.1 +log +@Initial revision +@ +text +@d1 2 +a2 1 +This is vtag-1 (on vbranchA) of imported-modified-imported.txt. +@ + + +1.1.1.1 +log +@Import (vbranchA, vtag-1). +@ +text +@@ + + +1.1.1.2 +log +@Import (vbranchA, vtag-2). +@ +text +@d1 1 +a1 1 +This is vtag-2 (on vbranchA) of imported-modified-imported.txt. +@ + + diff --git a/t/t9601/cvsroot/module/imported-modified.txt,v b/t/t9601/cvsroot/module/imported-modified.txt,v new file mode 100644 index 000000000..bbcfe447b --- /dev/null +++ b/t/t9601/cvsroot/module/imported-modified.txt,v @@ -0,0 +1,59 @@ +head 1.2; +access; +symbols + vtag-1:1.1.1.1 + vbranchA:1.1.1; +locks; strict; +comment @# @; + + +1.2 +date 2004.02.09.15.43.14; author kfogel; state Exp; +branches; +next 1.1; + +1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next ; + + +desc +@@ + + +1.2 +log +@Commit on HEAD. +@ +text +@This is a modification of imported-modified.txt on HEAD. +It should supersede the version from the vendor branch. +@ + + +1.1 +log +@Initial revision +@ +text +@d1 2 +a2 1 +This is vtag-1 (on vbranchA) of imported-modified.txt. +@ + + +1.1.1.1 +log +@Import (vbranchA, vtag-1). +@ +text +@@ + + diff --git a/t/t9601/cvsroot/module/imported-once.txt,v b/t/t9601/cvsroot/module/imported-once.txt,v new file mode 100644 index 000000000..c5dd82b12 --- /dev/null +++ b/t/t9601/cvsroot/module/imported-once.txt,v @@ -0,0 +1,43 @@ +head 1.1; +branch 1.1.1; +access; +symbols + vtag-1:1.1.1.1 + vbranchA:1.1.1; +locks; strict; +comment @# @; + + +1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next ; + + +desc +@@ + + +1.1 +log +@Initial revision +@ +text +@This is vtag-1 (on vbranchA) of imported-once.txt. +@ + + +1.1.1.1 +log +@Import (vbranchA, vtag-1). +@ +text +@@ + + diff --git a/t/t9601/cvsroot/module/imported-twice.txt,v b/t/t9601/cvsroot/module/imported-twice.txt,v new file mode 100644 index 000000000..d1f3f1b34 --- /dev/null +++ b/t/t9601/cvsroot/module/imported-twice.txt,v @@ -0,0 +1,60 @@ +head 1.1; +branch 1.1.1; +access; +symbols + vtag-2:1.1.1.2 + vtag-1:1.1.1.1 + vbranchA:1.1.1; +locks; strict; +comment @# @; + + +1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next 1.1.1.2; + +1.1.1.2 +date 2004.02.09.15.43.13; author kfogel; state Exp; +branches; +next ; + + +desc +@@ + + +1.1 +log +@Initial revision +@ +text +@This is vtag-1 (on vbranchA) of imported-twice.txt. +@ + + +1.1.1.1 +log +@Import (vbranchA, vtag-1). +@ +text +@@ + + +1.1.1.2 +log +@Import (vbranchA, vtag-2). +@ +text +@d1 1 +a1 1 +This is vtag-2 (on vbranchA) of imported-twice.txt. +@ + + diff --git a/t/t9602-cvsimport-branches-tags.sh b/t/t9602-cvsimport-branches-tags.sh new file mode 100755 index 000000000..67878b2d0 --- /dev/null +++ b/t/t9602-cvsimport-branches-tags.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +# A description of the repository used for this test can be found in +# t9602/README. + +test_description='git cvsimport handling of branches and tags' +. ./lib-cvs.sh + +CVSROOT="$TEST_DIRECTORY"/t9602/cvsroot +export CVSROOT + +test_expect_success 'import module' ' + + git cvsimport -C module-git module + +' + +test_expect_success 'test branch master' ' + + test_cmp_branch_tree master + +' + +test_expect_success 'test branch vendorbranch' ' + + test_cmp_branch_tree vendorbranch + +' + +test_expect_failure 'test branch B_FROM_INITIALS' ' + + test_cmp_branch_tree B_FROM_INITIALS + +' + +test_expect_failure 'test branch B_FROM_INITIALS_BUT_ONE' ' + + test_cmp_branch_tree B_FROM_INITIALS_BUT_ONE + +' + +test_expect_failure 'test branch B_MIXED' ' + + test_cmp_branch_tree B_MIXED + +' + +test_expect_success 'test branch B_SPLIT' ' + + test_cmp_branch_tree B_SPLIT + +' + +test_expect_failure 'test tag vendortag' ' + + test_cmp_branch_tree vendortag + +' + +test_expect_success 'test tag T_ALL_INITIAL_FILES' ' + + test_cmp_branch_tree T_ALL_INITIAL_FILES + +' + +test_expect_failure 'test tag T_ALL_INITIAL_FILES_BUT_ONE' ' + + test_cmp_branch_tree T_ALL_INITIAL_FILES_BUT_ONE + +' + +test_expect_failure 'test tag T_MIXED' ' + + test_cmp_branch_tree T_MIXED + +' + + +test_done diff --git a/t/t9602/README b/t/t9602/README new file mode 100644 index 000000000..c231e0f26 --- /dev/null +++ b/t/t9602/README @@ -0,0 +1,62 @@ +This repository is for testing the ability to group revisions +correctly along tags and branches. Here is its history: + + 1. The initial import (revision 1.1 of everybody) created a + directory structure with a file named `default' in each dir: + + ./ + default + sub1/default + subsubA/default + subsubB/default + sub2/default + subsubA/default + sub3/default + + 2. Then tagged everyone with T_ALL_INITIAL_FILES. + + 3. Then tagged everyone except sub1/subsubB/default with + T_ALL_INITIAL_FILES_BUT_ONE. + + 4. Then created branch B_FROM_INITIALS on everyone. + + 5. Then created branch B_FROM_INITIALS_BUT_ONE on everyone except + /sub1/subsubB/default. + + 6. Then committed modifications to two files: sub3/default, and + sub1/subsubA/default. + + 7. Then committed a modification to all 7 files. + + 8. Then backdated sub3/default to revision 1.2, and + sub2/subsubA/default to revision 1.1, and tagged with T_MIXED. + + 9. Same as 8, but tagged with -b to create branch B_MIXED. + + 10. Switched the working copy to B_MIXED, and added + sub2/branch_B_MIXED_only. (That's why the RCS file is in + sub2/Attic/ -- it never existed on trunk.) + + 11. In one commit, modified default, sub1/default, and + sub2/subsubA/default, on branch B_MIXED. + + 12. Did "cvs up -A" on sub2/default, then in one commit, made a + change to sub2/default and sub2/branch_B_MIXED_only. So this + commit should be spread between the branch and the trunk. + + 13. Do "cvs up -A" to get everyone back to trunk, then make a new + branch B_SPLIT on everyone except sub1/subsubB/default,v. + + 14. Switch to branch B_SPLIT (see sub1/subsubB/default disappear) + and commit a change that affects everyone except sub3/default. + + 15. An hour or so later, "cvs up -A" to get sub1/subsubB/default + back, then commit a change on that file, on trunk. (It's + important that this change happened after the previous commits + on B_SPLIT.) + + 16. Branch sub1/subsubB/default to B_SPLIT, then "cvs up -r B_SPLIT" + to switch the whole working copy to the branch. + + 17. Commit a change on B_SPLIT, to sub1/subsubB/default and + sub3/default. diff --git a/t/t9602/cvsroot/.gitattributes b/t/t9602/cvsroot/.gitattributes new file mode 100644 index 000000000..562b12e16 --- /dev/null +++ b/t/t9602/cvsroot/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/t/t9602/cvsroot/CVSROOT/.gitignore b/t/t9602/cvsroot/CVSROOT/.gitignore new file mode 100644 index 000000000..3bb9b3417 --- /dev/null +++ b/t/t9602/cvsroot/CVSROOT/.gitignore @@ -0,0 +1,2 @@ +history +val-tags diff --git a/t/t9602/cvsroot/module/default,v b/t/t9602/cvsroot/module/default,v new file mode 100644 index 000000000..3b68382a3 --- /dev/null +++ b/t/t9602/cvsroot/module/default,v @@ -0,0 +1,102 @@ +head 1.2; +access; +symbols + B_SPLIT:1.2.0.4 + B_MIXED:1.2.0.2 + T_MIXED:1.2 + B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.2 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches + 1.2.2.1 + 1.2.4.1; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.2.2.1 +date 2003.05.23.00.31.36; author jrandom; state Exp; +branches; +next ; + +1.2.4.1 +date 2003.06.03.03.20.31; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.2 +log +@Second commit to proj, affecting all 7 files. +@ +text +@This is the file `default' in the top level of the project. + +Every directory in the `proj' project has a file named `default'. + +This line was added in the second commit (affecting all 7 files). +@ + + +1.2.4.1 +log +@First change on branch B_SPLIT. + +This change excludes sub3/default, because it was not part of this +commit, and sub1/subsubB/default, which is not even on the branch yet. +@ +text +@a5 2 + +First change on branch B_SPLIT. +@ + + +1.2.2.1 +log +@Modify three files, on branch B_MIXED. +@ +text +@a5 2 + +This line was added on branch B_MIXED only (affecting 3 files). +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9602/cvsroot/module/sub1/default,v b/t/t9602/cvsroot/module/sub1/default,v new file mode 100644 index 000000000..b7fdccdfd --- /dev/null +++ b/t/t9602/cvsroot/module/sub1/default,v @@ -0,0 +1,102 @@ +head 1.2; +access; +symbols + B_SPLIT:1.2.0.4 + B_MIXED:1.2.0.2 + T_MIXED:1.2 + B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.2 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches + 1.2.2.1 + 1.2.4.1; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.2.2.1 +date 2003.05.23.00.31.36; author jrandom; state Exp; +branches; +next ; + +1.2.4.1 +date 2003.06.03.03.20.31; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.2 +log +@Second commit to proj, affecting all 7 files. +@ +text +@This is sub1/default. + +Every directory in the `proj' project has a file named `default'. + +This line was added in the second commit (affecting all 7 files). +@ + + +1.2.4.1 +log +@First change on branch B_SPLIT. + +This change excludes sub3/default, because it was not part of this +commit, and sub1/subsubB/default, which is not even on the branch yet. +@ +text +@a5 2 + +First change on branch B_SPLIT. +@ + + +1.2.2.1 +log +@Modify three files, on branch B_MIXED. +@ +text +@a5 2 + +This line was added on branch B_MIXED only (affecting 3 files). +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9602/cvsroot/module/sub1/subsubA/default,v b/t/t9602/cvsroot/module/sub1/subsubA/default,v new file mode 100644 index 000000000..472b7b2bd --- /dev/null +++ b/t/t9602/cvsroot/module/sub1/subsubA/default,v @@ -0,0 +1,101 @@ +head 1.3; +access; +symbols + B_SPLIT:1.3.0.4 + B_MIXED:1.3.0.2 + T_MIXED:1.3 + B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.3 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches + 1.3.4.1; +next 1.2; + +1.2 +date 2003.05.23.00.15.26; author jrandom; state Exp; +branches; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.3.4.1 +date 2003.06.03.03.20.31; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.3 +log +@Second commit to proj, affecting all 7 files. +@ +text +@This is sub1/subsubA/default. + +Every directory in the `proj' project has a file named `default'. + +This line was added by the first commit (affecting two files). + +This line was added in the second commit (affecting all 7 files). +@ + + +1.3.4.1 +log +@First change on branch B_SPLIT. + +This change excludes sub3/default, because it was not part of this +commit, and sub1/subsubB/default, which is not even on the branch yet. +@ +text +@a7 2 + +First change on branch B_SPLIT. +@ + + +1.2 +log +@First commit to proj, affecting two files. +@ +text +@d6 2 +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9602/cvsroot/module/sub1/subsubB/default,v b/t/t9602/cvsroot/module/sub1/subsubB/default,v new file mode 100644 index 000000000..fe6efa455 --- /dev/null +++ b/t/t9602/cvsroot/module/sub1/subsubB/default,v @@ -0,0 +1,107 @@ +head 1.3; +access; +symbols + B_SPLIT:1.3.0.2 + B_MIXED:1.2.0.2 + T_MIXED:1.2 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.3 +date 2003.06.03.04.29.14; author jrandom; state Exp; +branches + 1.3.2.1; +next 1.2; + +1.2 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.3.2.1 +date 2003.06.03.04.33.13; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.3 +log +@A trunk change to sub1/subsubB/default. This was committed about an +hour after an earlier change that affected most files on branch +B_SPLIT. This file is not on that branch yet, but after this commit, +we'll branch to B_SPLIT, albeit rooted in a revision that didn't exist +at the time the rest of B_SPLIT was created. +@ +text +@This is sub1/subsubB/default. + +Every directory in the `proj' project has a file named `default'. + +This line was added in the second commit (affecting all 7 files). + +This bit was committed on trunk about an hour after an earlier change +to everyone else on branch B_SPLIT. Afterwards, we'll finally branch +this file to B_SPLIT, but rooted in a revision that didn't exist at +the time the rest of B_SPLIT was created. +@ + + +1.3.2.1 +log +@This change affects sub3/default and sub1/subsubB/default, on branch +B_SPLIT. Note that the latter file did not even exist on this branch +until after some other files had had revisions committed on B_SPLIT. +@ +text +@a10 4 + +This change affects sub3/default and sub1/subsubB/default, on branch +B_SPLIT. Note that the latter file did not even exist on this branch +until after some other files had had revisions committed on B_SPLIT. +@ + + +1.2 +log +@Second commit to proj, affecting all 7 files. +@ +text +@d6 5 +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v b/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v new file mode 100644 index 000000000..34c9789f2 --- /dev/null +++ b/t/t9602/cvsroot/module/sub2/Attic/branch_B_MIXED_only,v @@ -0,0 +1,59 @@ +head 1.1; +access; +symbols + B_MIXED:1.1.0.2; +locks; strict; +comment @# @; + + +1.1 +date 2003.05.23.00.25.26; author jrandom; state dead; +branches + 1.1.2.1; +next ; + +1.1.2.1 +date 2003.05.23.00.25.26; author jrandom; state Exp; +branches; +next 1.1.2.2; + +1.1.2.2 +date 2003.05.23.00.48.51; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.1 +log +@file branch_B_MIXED_only was initially added on branch B_MIXED. +@ +text +@@ + + +1.1.2.1 +log +@Add a file on branch B_MIXED. +@ +text +@a0 1 +This file was added on branch B_MIXED. It never existed on trunk. +@ + + +1.1.2.2 +log +@A single commit affecting one file on branch B_MIXED and one on trunk. +@ +text +@a1 3 + +The same commit added these two lines here on branch B_MIXED, and two +similar lines to ./default on trunk. +@ + + diff --git a/t/t9602/cvsroot/module/sub2/default,v b/t/t9602/cvsroot/module/sub2/default,v new file mode 100644 index 000000000..018f7f8ec --- /dev/null +++ b/t/t9602/cvsroot/module/sub2/default,v @@ -0,0 +1,102 @@ +head 1.3; +access; +symbols + B_SPLIT:1.3.0.2 + B_MIXED:1.2.0.2 + T_MIXED:1.2 + B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.3 +date 2003.05.23.00.48.51; author jrandom; state Exp; +branches + 1.3.2.1; +next 1.2; + +1.2 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.3.2.1 +date 2003.06.03.03.20.31; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.3 +log +@A single commit affecting one file on branch B_MIXED and one on trunk. +@ +text +@This is sub2/default. + +Every directory in the `proj' project has a file named `default'. + +This line was added in the second commit (affecting all 7 files). + +The same commit added these two lines here on trunk, and two similar +lines to ./branch_B_MIXED_only on branch B_MIXED. +@ + + +1.3.2.1 +log +@First change on branch B_SPLIT. + +This change excludes sub3/default, because it was not part of this +commit, and sub1/subsubB/default, which is not even on the branch yet. +@ +text +@a8 2 + +First change on branch B_SPLIT. +@ + + +1.2 +log +@Second commit to proj, affecting all 7 files. +@ +text +@d6 3 +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9602/cvsroot/module/sub2/subsubA/default,v b/t/t9602/cvsroot/module/sub2/subsubA/default,v new file mode 100644 index 000000000..d13242cb0 --- /dev/null +++ b/t/t9602/cvsroot/module/sub2/subsubA/default,v @@ -0,0 +1,102 @@ +head 1.2; +access; +symbols + B_SPLIT:1.2.0.2 + B_MIXED:1.1.0.2 + T_MIXED:1.1 + B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.2 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches + 1.2.2.1; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1 + 1.1.2.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.1.2.1 +date 2003.05.23.00.31.36; author jrandom; state Exp; +branches; +next ; + +1.2.2.1 +date 2003.06.03.03.20.31; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.2 +log +@Second commit to proj, affecting all 7 files. +@ +text +@This is sub2/subsub2/default. + +Every directory in the `proj' project has a file named `default'. + +This line was added in the second commit (affecting all 7 files). +@ + + +1.2.2.1 +log +@First change on branch B_SPLIT. + +This change excludes sub3/default, because it was not part of this +commit, and sub1/subsubB/default, which is not even on the branch yet. +@ +text +@a5 2 + +First change on branch B_SPLIT. +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.2.1 +log +@Modify three files, on branch B_MIXED. +@ +text +@a3 2 + +This line was added on branch B_MIXED only (affecting 3 files). +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9602/cvsroot/module/sub3/default,v b/t/t9602/cvsroot/module/sub3/default,v new file mode 100644 index 000000000..88e456743 --- /dev/null +++ b/t/t9602/cvsroot/module/sub3/default,v @@ -0,0 +1,102 @@ +head 1.3; +access; +symbols + B_SPLIT:1.3.0.2 + B_MIXED:1.2.0.2 + T_MIXED:1.2 + B_FROM_INITIALS_BUT_ONE:1.1.1.1.0.4 + B_FROM_INITIALS:1.1.1.1.0.2 + T_ALL_INITIAL_FILES_BUT_ONE:1.1.1.1 + T_ALL_INITIAL_FILES:1.1.1.1 + vendortag:1.1.1.1 + vendorbranch:1.1.1; +locks; strict; +comment @# @; + + +1.3 +date 2003.05.23.00.17.53; author jrandom; state Exp; +branches + 1.3.2.1; +next 1.2; + +1.2 +date 2003.05.23.00.15.26; author jrandom; state Exp; +branches; +next 1.1; + +1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches + 1.1.1.1; +next ; + +1.1.1.1 +date 2003.05.22.23.20.19; author jrandom; state Exp; +branches; +next ; + +1.3.2.1 +date 2003.06.03.04.33.13; author jrandom; state Exp; +branches; +next ; + + +desc +@@ + + +1.3 +log +@Second commit to proj, affecting all 7 files. +@ +text +@This is sub3/default. + +Every directory in the `proj' project has a file named `default'. + +This line was added by the first commit (affecting two files). + +This line was added in the second commit (affecting all 7 files). +@ + + +1.3.2.1 +log +@This change affects sub3/default and sub1/subsubB/default, on branch +B_SPLIT. Note that the latter file did not even exist on this branch +until after some other files had had revisions committed on B_SPLIT. +@ +text +@a7 4 + +This change affects sub3/default and sub1/subsubB/default, on branch +B_SPLIT. Note that the latter file did not even exist on this branch +until after some other files had had revisions committed on B_SPLIT. +@ + + +1.2 +log +@First commit to proj, affecting two files. +@ +text +@d6 2 +@ + + +1.1 +log +@Initial revision +@ +text +@d4 2 +@ + + +1.1.1.1 +log +@Initial import. +@ +text +@@ diff --git a/t/t9603-cvsimport-patchsets.sh b/t/t9603-cvsimport-patchsets.sh new file mode 100755 index 000000000..958bdce4d --- /dev/null +++ b/t/t9603-cvsimport-patchsets.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +# Structure of the test cvs repository +# +# Message File:Content Commit Time +# Rev 1 a: 1.1 2009-02-21 19:11:43 +0100 +# Rev 2 a: 1.2 b: 1.1 2009-02-21 19:11:14 +0100 +# Rev 3 b: 1.2 2009-02-21 19:11:43 +0100 +# +# As you can see the commit of Rev 3 has the same time as +# Rev 1 this leads to a broken import because of a cvsps +# bug. + +test_description='git cvsimport testing for correct patchset estimation' +. ./lib-cvs.sh + +CVSROOT="$TEST_DIRECTORY"/t9603/cvsroot +export CVSROOT + +test_expect_failure 'import with criss cross times on revisions' ' + + git cvsimport -p"-x" -C module-git module && + cd module-git && + git log --pretty=format:%s > ../actual-master && + git log A~2..A --pretty="format:%s %ad" -- > ../actual-A && + echo "" >> ../actual-master && + echo "" >> ../actual-A && + cd .. && + echo "Rev 4 +Rev 3 +Rev 2 +Rev 1" > expect-master && + test_cmp actual-master expect-master && + + echo "Rev 5 Branch A Wed Mar 11 19:09:10 2009 +0000 +Rev 4 Branch A Wed Mar 11 19:03:52 2009 +0000" > expect-A && + test_cmp actual-A expect-A +' + +test_done diff --git a/t/t9603/cvsroot/.gitattributes b/t/t9603/cvsroot/.gitattributes new file mode 100644 index 000000000..562b12e16 --- /dev/null +++ b/t/t9603/cvsroot/.gitattributes @@ -0,0 +1 @@ +* -whitespace diff --git a/t/t9603/cvsroot/CVSROOT/.gitignore b/t/t9603/cvsroot/CVSROOT/.gitignore new file mode 100644 index 000000000..3bb9b3417 --- /dev/null +++ b/t/t9603/cvsroot/CVSROOT/.gitignore @@ -0,0 +1,2 @@ +history +val-tags diff --git a/t/t9603/cvsroot/module/a,v b/t/t9603/cvsroot/module/a,v new file mode 100644 index 000000000..ba8fd5af2 --- /dev/null +++ b/t/t9603/cvsroot/module/a,v @@ -0,0 +1,74 @@ +head 1.2; +access; +symbols + A:1.2.0.2; +locks; strict; +comment @# @; + + +1.2 +date 2009.02.21.18.11.14; author tester; state Exp; +branches + 1.2.2.1; +next 1.1; + +1.1 +date 2009.02.21.18.11.43; author tester; state Exp; +branches; +next ; + +1.2.2.1 +date 2009.03.11.19.03.52; author tester; state Exp; +branches; +next 1.2.2.2; + +1.2.2.2 +date 2009.03.11.19.09.10; author tester; state Exp; +branches; +next ; + + +desc +@@ + + +1.2 +log +@Rev 2 +@ +text +@1.2 +@ + + +1.2.2.1 +log +@Rev 4 Branch A +@ +text +@d1 1 +a1 1 +1.2.2.1 +@ + + +1.2.2.2 +log +@Rev 5 Branch A +@ +text +@d1 1 +a1 1 +1.2.2.2 +@ + + +1.1 +log +@Rev 1 +@ +text +@d1 1 +a1 1 +1.1 +@ diff --git a/t/t9603/cvsroot/module/b,v b/t/t9603/cvsroot/module/b,v new file mode 100644 index 000000000..d26885518 --- /dev/null +++ b/t/t9603/cvsroot/module/b,v @@ -0,0 +1,90 @@ +head 1.3; +access; +symbols + A:1.2.0.2; +locks; strict; +comment @# @; + + +1.3 +date 2009.03.11.19.05.08; author tester; state Exp; +branches; +next 1.2; + +1.2 +date 2009.02.21.18.11.43; author tester; state Exp; +branches + 1.2.2.1; +next 1.1; + +1.1 +date 2009.02.21.18.11.14; author tester; state Exp; +branches; +next ; + +1.2.2.1 +date 2009.03.11.19.03.52; author tester; state Exp; +branches; +next 1.2.2.2; + +1.2.2.2 +date 2009.03.11.19.09.10; author tester; state Exp; +branches; +next ; + + +desc +@@ + + +1.3 +log +@Rev 4 +@ +text +@1.3 +@ + + +1.2 +log +@Rev 3 +@ +text +@d1 1 +a1 1 +1.2 +@ + + +1.2.2.1 +log +@Rev 4 Branch A +@ +text +@d1 1 +a1 1 +1.2.2.1 +@ + + +1.2.2.2 +log +@Rev 5 Branch A +@ +text +@d1 1 +a1 1 +1.2 +@ + + +1.1 +log +@Rev 2 +@ +text +@d1 1 +a1 1 +1.1 +@ diff --git a/t/test-lib.sh b/t/test-lib.sh index 5fdc5d94a..f2ca53647 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -114,6 +114,9 @@ do valgrind=t; verbose=t; shift ;; --tee) shift ;; # was handled already + --root=*) + root=$(expr "z$1" : 'z[^=]*=\(.*\)') + shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac @@ -645,7 +648,12 @@ fi # Test repository test="trash directory.$(basename "$0" .sh)" -test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test" +test -n "$root" && test="$root/$test" +case "$test" in +/*) TRASH_DIRECTORY="$test" ;; + *) TRASH_DIRECTORY="$TEST_DIRECTORY/$test" ;; +esac +test ! -z "$debug" || remove_trash=$TRASH_DIRECTORY rm -fr "$test" || { GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" @@ -677,6 +685,21 @@ do esac done +# Provide an implementation of the 'yes' utility +yes () { + if test $# = 0 + then + y=y + else + y="$*" + fi + + while echo "$y" + do + : + done +} + # Fix some commands on Windows case $(uname -s) in *MINGW*) diff --git a/test-date.c b/test-date.c index 62e8f2387..a9e705f79 100644 --- a/test-date.c +++ b/test-date.c @@ -1,20 +1,67 @@ #include "cache.h" -int main(int argc, char **argv) +static const char *usage_msg = "\n" +" test-date show [time_t]...\n" +" test-date parse [date]...\n" +" test-date approxidate [date]...\n"; + +static void show_dates(char **argv, struct timeval *now) { - int i; + char buf[128]; + + for (; *argv; argv++) { + time_t t = atoi(*argv); + show_date_relative(t, 0, now, buf, sizeof(buf)); + printf("%s -> %s\n", *argv, buf); + } +} - for (i = 1; i < argc; i++) { +static void parse_dates(char **argv, struct timeval *now) +{ + for (; *argv; argv++) { char result[100]; time_t t; - memcpy(result, "bad", 4); - parse_date(argv[i], result, sizeof(result)); + result[0] = 0; + parse_date(*argv, result, sizeof(result)); t = strtoul(result, NULL, 0); - printf("%s -> %s -> %s", argv[i], result, ctime(&t)); + printf("%s -> %s\n", *argv, + t ? show_date(t, 0, DATE_ISO8601) : "bad"); + } +} - t = approxidate(argv[i]); - printf("%s -> %s\n", argv[i], ctime(&t)); +static void parse_approxidate(char **argv, struct timeval *now) +{ + for (; *argv; argv++) { + time_t t; + t = approxidate_relative(*argv, now); + printf("%s -> %s\n", *argv, show_date(t, 0, DATE_ISO8601)); + } +} + +int main(int argc, char **argv) +{ + struct timeval now; + const char *x; + + x = getenv("TEST_DATE_NOW"); + if (x) { + now.tv_sec = atoi(x); + now.tv_usec = 0; } + else + gettimeofday(&now, NULL); + + argv++; + if (!*argv) + usage(usage_msg); + if (!strcmp(*argv, "show")) + show_dates(argv+1, &now); + else if (!strcmp(*argv, "parse")) + parse_dates(argv+1, &now); + else if (!strcmp(*argv, "approxidate")) + parse_approxidate(argv+1, &now); + else + usage(usage_msg); return 0; } diff --git a/test-delta.c b/test-delta.c index 3d885ff37..af40a3c49 100644 --- a/test-delta.c +++ b/test-delta.c @@ -1,7 +1,7 @@ /* * test-delta.c: test code to exercise diff-delta.c and patch-delta.c * - * (C) 2005 Nicolas Pitre <nico@cam.org> + * (C) 2005 Nicolas Pitre <nico@fluxnic.net> * * This code is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as diff --git a/transport-helper.c b/transport-helper.c new file mode 100644 index 000000000..f57e84c67 --- /dev/null +++ b/transport-helper.c @@ -0,0 +1,168 @@ +#include "cache.h" +#include "transport.h" + +#include "run-command.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" + +struct helper_data +{ + const char *name; + struct child_process *helper; + unsigned fetch : 1; +}; + +static struct child_process *get_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + struct strbuf buf = STRBUF_INIT; + struct child_process *helper; + FILE *file; + + if (data->helper) + return data->helper; + + helper = xcalloc(1, sizeof(*helper)); + helper->in = -1; + helper->out = -1; + helper->err = 0; + helper->argv = xcalloc(4, sizeof(*helper->argv)); + strbuf_addf(&buf, "remote-%s", data->name); + helper->argv[0] = strbuf_detach(&buf, NULL); + helper->argv[1] = transport->remote->name; + helper->argv[2] = transport->url; + helper->git_cmd = 1; + if (start_command(helper)) + die("Unable to run helper: git %s", helper->argv[0]); + data->helper = helper; + + write_str_in_full(helper->in, "capabilities\n"); + + file = xfdopen(helper->out, "r"); + while (1) { + if (strbuf_getline(&buf, file, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!*buf.buf) + break; + if (!strcmp(buf.buf, "fetch")) + data->fetch = 1; + } + return data->helper; +} + +static int disconnect_helper(struct transport *transport) +{ + struct helper_data *data = transport->data; + if (data->helper) { + write_str_in_full(data->helper->in, "\n"); + close(data->helper->in); + finish_command(data->helper); + free((char *)data->helper->argv[0]); + free(data->helper->argv); + free(data->helper); + data->helper = NULL; + } + return 0; +} + +static int fetch_with_fetch(struct transport *transport, + int nr_heads, const struct ref **to_fetch) +{ + struct child_process *helper = get_helper(transport); + FILE *file = xfdopen(helper->out, "r"); + int i; + struct strbuf buf = STRBUF_INIT; + + for (i = 0; i < nr_heads; i++) { + const struct ref *posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + + strbuf_addf(&buf, "fetch %s %s\n", + sha1_to_hex(posn->old_sha1), posn->name); + write_in_full(helper->in, buf.buf, buf.len); + strbuf_reset(&buf); + + if (strbuf_getline(&buf, file, '\n') == EOF) + exit(128); /* child died, message supplied already */ + } + return 0; +} + +static int fetch(struct transport *transport, + int nr_heads, const struct ref **to_fetch) +{ + struct helper_data *data = transport->data; + int i, count; + + count = 0; + for (i = 0; i < nr_heads; i++) + if (!(to_fetch[i]->status & REF_STATUS_UPTODATE)) + count++; + + if (!count) + return 0; + + if (data->fetch) + return fetch_with_fetch(transport, nr_heads, to_fetch); + + return -1; +} + +static struct ref *get_refs_list(struct transport *transport, int for_push) +{ + struct child_process *helper; + struct ref *ret = NULL; + struct ref **tail = &ret; + struct ref *posn; + struct strbuf buf = STRBUF_INIT; + FILE *file; + + helper = get_helper(transport); + + write_str_in_full(helper->in, "list\n"); + + file = xfdopen(helper->out, "r"); + while (1) { + char *eov, *eon; + if (strbuf_getline(&buf, file, '\n') == EOF) + exit(128); /* child died, message supplied already */ + + if (!*buf.buf) + break; + + eov = strchr(buf.buf, ' '); + if (!eov) + die("Malformed response in ref list: %s", buf.buf); + eon = strchr(eov + 1, ' '); + *eov = '\0'; + if (eon) + *eon = '\0'; + *tail = alloc_ref(eov + 1); + if (buf.buf[0] == '@') + (*tail)->symref = xstrdup(buf.buf + 1); + else if (buf.buf[0] != '?') + get_sha1_hex(buf.buf, (*tail)->old_sha1); + tail = &((*tail)->next); + } + strbuf_release(&buf); + + for (posn = ret; posn; posn = posn->next) + resolve_remote_symref(posn, ret); + + return ret; +} + +int transport_helper_init(struct transport *transport, const char *name) +{ + struct helper_data *data = xcalloc(sizeof(*data), 1); + data->name = name; + + transport->data = data; + transport->get_refs_list = get_refs_list; + transport->fetch = fetch; + transport->disconnect = disconnect_helper; + return 0; +} diff --git a/transport.c b/transport.c index f231b355f..4cb807700 100644 --- a/transport.c +++ b/transport.c @@ -1,9 +1,6 @@ #include "cache.h" #include "transport.h" #include "run-command.h" -#ifndef NO_CURL -#include "http.h" -#endif #include "pkt-line.h" #include "fetch-pack.h" #include "send-pack.h" @@ -352,51 +349,11 @@ static int rsync_transport_push(struct transport *transport, return result; } -/* Generic functions for using commit walkers */ - -#ifndef NO_CURL /* http fetch is the only user */ -static int fetch_objs_via_walker(struct transport *transport, - int nr_objs, const struct ref **to_fetch) -{ - char *dest = xstrdup(transport->url); - struct walker *walker = transport->data; - char **objs = xmalloc(nr_objs * sizeof(*objs)); - int i; - - walker->get_all = 1; - walker->get_tree = 1; - walker->get_history = 1; - walker->get_verbosely = transport->verbose >= 0; - walker->get_recover = 0; - - for (i = 0; i < nr_objs; i++) - objs[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1)); - - if (walker_fetch(walker, nr_objs, objs, NULL, NULL)) - die("Fetch failed."); - - for (i = 0; i < nr_objs; i++) - free(objs[i]); - free(objs); - free(dest); - return 0; -} -#endif /* NO_CURL */ - -static int disconnect_walker(struct transport *transport) -{ - struct walker *walker = transport->data; - if (walker) - walker_free(walker); - return 0; -} - #ifndef NO_CURL static int curl_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) { const char **argv; int argc; - int err; if (flags & TRANSPORT_PUSH_MIRROR) return error("http transport does not support mirror mode"); @@ -416,110 +373,7 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons while (refspec_nr--) argv[argc++] = *refspec++; argv[argc] = NULL; - err = run_command_v_opt(argv, RUN_GIT_CMD); - switch (err) { - case -ERR_RUN_COMMAND_FORK: - error("unable to fork for %s", argv[0]); - case -ERR_RUN_COMMAND_EXEC: - error("unable to exec %s", argv[0]); - break; - case -ERR_RUN_COMMAND_WAITPID: - case -ERR_RUN_COMMAND_WAITPID_WRONG_PID: - case -ERR_RUN_COMMAND_WAITPID_SIGNAL: - case -ERR_RUN_COMMAND_WAITPID_NOEXIT: - error("%s died with strange error", argv[0]); - } - return !!err; -} - -static struct ref *get_refs_via_curl(struct transport *transport, int for_push) -{ - struct strbuf buffer = STRBUF_INIT; - char *data, *start, *mid; - char *ref_name; - char *refs_url; - int i = 0; - int http_ret; - - struct ref *refs = NULL; - struct ref *ref = NULL; - struct ref *last_ref = NULL; - - struct walker *walker; - - if (for_push) - return NULL; - - if (!transport->data) - transport->data = get_http_walker(transport->url, - transport->remote); - - walker = transport->data; - - refs_url = xmalloc(strlen(transport->url) + 11); - sprintf(refs_url, "%s/info/refs", transport->url); - - http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); - switch (http_ret) { - case HTTP_OK: - break; - case HTTP_MISSING_TARGET: - die("%s not found: did you run git update-server-info on the" - " server?", refs_url); - default: - http_error(refs_url, http_ret); - die("HTTP request failed"); - } - - data = buffer.buf; - start = NULL; - mid = data; - while (i < buffer.len) { - if (!start) - start = &data[i]; - if (data[i] == '\t') - mid = &data[i]; - if (data[i] == '\n') { - data[i] = 0; - ref_name = mid + 1; - ref = xmalloc(sizeof(struct ref) + - strlen(ref_name) + 1); - memset(ref, 0, sizeof(struct ref)); - strcpy(ref->name, ref_name); - get_sha1_hex(start, ref->old_sha1); - if (!refs) - refs = ref; - if (last_ref) - last_ref->next = ref; - last_ref = ref; - start = NULL; - } - i++; - } - - strbuf_release(&buffer); - - ref = alloc_ref("HEAD"); - if (!walker->fetch_ref(walker, ref) && - !resolve_remote_symref(ref, refs)) { - ref->next = refs; - refs = ref; - } else { - free(ref); - } - - strbuf_release(&buffer); - free(refs_url); - return refs; -} - -static int fetch_objs_via_curl(struct transport *transport, - int nr_objs, const struct ref **to_fetch) -{ - if (!transport->data) - transport->data = get_http_walker(transport->url, - transport->remote); - return fetch_objs_via_walker(transport, nr_objs, to_fetch); + return !!run_command_v_opt(argv, RUN_GIT_CMD); } #endif @@ -681,6 +535,21 @@ static int fetch_refs_via_pack(struct transport *transport, return (refs ? 0 : -1); } +static int push_had_errors(struct ref *ref) +{ + for (; ref; ref = ref->next) { + switch (ref->status) { + case REF_STATUS_NONE: + case REF_STATUS_UPTODATE: + case REF_STATUS_OK: + break; + default: + return 1; + } + } + return 0; +} + static int refs_pushed(struct ref *ref) { for (; ref; ref = ref->next) { @@ -895,6 +764,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->thin; args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); + args.quiet = !!(flags & TRANSPORT_PUSH_QUIET); args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); ret = send_pack(&args, data->fd, data->conn, remote_refs, @@ -953,14 +823,12 @@ struct transport *transport_get(struct remote *remote, const char *url) } else if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://") || !prefixcmp(url, "ftp://")) { + transport_helper_init(ret, "curl"); #ifdef NO_CURL error("git was compiled without libcurl support."); #else - ret->get_refs_list = get_refs_via_curl; - ret->fetch = fetch_objs_via_curl; ret->push = curl_transport_push; #endif - ret->disconnect = disconnect_walker; } else if (is_local(url) && is_file(url)) { struct bundle_transport_data *data = xcalloc(1, sizeof(*data)); @@ -1013,6 +881,7 @@ int transport_push(struct transport *transport, struct ref *local_refs = get_local_heads(); int match_flags = MATCH_REFS_NONE; int verbose = flags & TRANSPORT_PUSH_VERBOSE; + int quiet = flags & TRANSPORT_PUSH_QUIET; int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; int ret; @@ -1028,7 +897,10 @@ int transport_push(struct transport *transport, ret = transport->push_refs(transport, remote_refs, flags); - print_push_status(transport->url, remote_refs, verbose | porcelain, porcelain, nonfastforward); + if (!quiet || push_had_errors(remote_refs)) + print_push_status(transport->url, remote_refs, + verbose | porcelain, porcelain, + nonfastforward); if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { struct ref *ref; @@ -1036,7 +908,7 @@ int transport_push(struct transport *transport, update_tracking_ref(transport->remote, ref, verbose); } - if (!ret && !refs_pushed(remote_refs)) + if (!quiet && !ret && !refs_pushed(remote_refs)) fprintf(stderr, "Everything up-to-date\n"); return ret; } @@ -1053,11 +925,12 @@ const struct ref *transport_get_remote_refs(struct transport *transport) int transport_fetch_refs(struct transport *transport, const struct ref *refs) { int rc; - int nr_heads = 0, nr_alloc = 0; + int nr_heads = 0, nr_alloc = 0, nr_refs = 0; const struct ref **heads = NULL; const struct ref *rm; for (rm = refs; rm; rm = rm->next) { + nr_refs++; if (rm->peer_ref && !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1)) continue; @@ -1065,6 +938,19 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs) heads[nr_heads++] = rm; } + if (!nr_heads) { + /* + * When deepening of a shallow repository is requested, + * then local and remote refs are likely to still be equal. + * Just feed them all to the fetch method in that case. + * This condition shouldn't be met in a non-deepening fetch + * (see builtin-fetch.c:quickfetch()). + */ + heads = xmalloc(nr_refs * sizeof(*heads)); + for (rm = refs; rm; rm = rm->next) + heads[nr_heads++] = rm; + } + rc = transport->fetch(transport, nr_heads, heads); free(heads); return rc; diff --git a/transport.h b/transport.h index 639f13dcf..c14da6f1e 100644 --- a/transport.h +++ b/transport.h @@ -36,6 +36,7 @@ struct transport { #define TRANSPORT_PUSH_MIRROR 8 #define TRANSPORT_PUSH_VERBOSE 16 #define TRANSPORT_PUSH_PORCELAIN 32 +#define TRANSPORT_PUSH_QUIET 64 /* Returns a transport suitable for the url */ struct transport *transport_get(struct remote *, const char *); @@ -78,4 +79,7 @@ void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); +/* Transport methods defined outside transport.c */ +int transport_helper_init(struct transport *transport, const char *name); + #endif diff --git a/unpack-trees.h b/unpack-trees.h index 1e0e2325f..d19df44f4 100644 --- a/unpack-trees.h +++ b/unpack-trees.h @@ -17,18 +17,18 @@ struct unpack_trees_error_msgs { }; struct unpack_trees_options { - unsigned int reset:1, - merge:1, - update:1, - index_only:1, - nontrivial_merge:1, - trivial_merges_only:1, - verbose_update:1, - aggressive:1, - skip_unmerged:1, - initial_checkout:1, - diff_index_cached:1, - gently:1; + unsigned int reset, + merge, + update, + index_only, + nontrivial_merge, + trivial_merges_only, + verbose_update, + aggressive, + skip_unmerged, + initial_checkout, + diff_index_cached, + gently; const char *prefix; int pos; struct dir_struct *dir; diff --git a/update-server-info.c b/update-server-info.c deleted file mode 100644 index 7b38fd867..000000000 --- a/update-server-info.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "cache.h" -#include "exec_cmd.h" - -static const char update_server_info_usage[] = -"git update-server-info [--force]"; - -int main(int ac, char **av) -{ - int i; - int force = 0; - for (i = 1; i < ac; i++) { - if (av[i][0] == '-') { - if (!strcmp("--force", av[i]) || - !strcmp("-f", av[i])) - force = 1; - else - usage(update_server_info_usage); - } - } - if (i != ac) - usage(update_server_info_usage); - - git_extract_argv0_path(av[0]); - - setup_git_directory(); - - return !!update_server_info(force); -} diff --git a/upload-pack.c b/upload-pack.c index 841ebb534..38ddac2e8 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -29,8 +29,10 @@ static unsigned long oldest_have; static int multi_ack, nr_our_refs; static int use_thin_pack, use_ofs_delta, use_include_tag; static int no_progress, daemon_mode; +static int shallow_nr; static struct object_array have_obj; static struct object_array want_obj; +static struct object_array extra_edge_obj; static unsigned int timeout; /* 0 for no sideband, * otherwise maximum packet size (up to 65520 bytes). @@ -106,9 +108,7 @@ static int do_rev_list(int fd, void *create_full_pack) int i; struct rev_info revs; - pack_pipe = fdopen(fd, "w"); - if (create_full_pack) - use_thin_pack = 0; /* no point doing it */ + pack_pipe = xfdopen(fd, "w"); init_revisions(&revs, NULL); revs.tag_objects = 1; revs.tree_objects = 1; @@ -136,14 +136,76 @@ static int do_rev_list(int fd, void *create_full_pack) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); + if (use_thin_pack) + for (i = 0; i < extra_edge_obj.nr; i++) + fprintf(pack_pipe, "-%s\n", sha1_to_hex( + extra_edge_obj.objects[i].item->sha1)); traverse_commit_list(&revs, show_commit, show_object, NULL); fflush(pack_pipe); fclose(pack_pipe); return 0; } +static int feed_msg_to_hook(int fd, const char *fmt, ...) +{ + int cnt; + char buf[1024]; + va_list params; + + va_start(params, fmt); + cnt = vsprintf(buf, fmt, params); + va_end(params); + return write_in_full(fd, buf, cnt) != cnt; +} + +static int feed_obj_to_hook(const char *label, struct object_array *oa, int i, int fd) +{ + return feed_msg_to_hook(fd, "%s %s\n", label, + sha1_to_hex(oa->objects[i].item->sha1)); +} + +static int run_post_upload_pack_hook(size_t total, struct timeval *tv) +{ + const char *argv[2]; + struct child_process proc; + int err, i; + + argv[0] = "hooks/post-upload-pack"; + argv[1] = NULL; + + if (access(argv[0], X_OK) < 0) + return 0; + + memset(&proc, 0, sizeof(proc)); + proc.argv = argv; + proc.in = -1; + proc.stdout_to_stderr = 1; + err = start_command(&proc); + if (err) + return err; + for (i = 0; !err && i < want_obj.nr; i++) + err |= feed_obj_to_hook("want", &want_obj, i, proc.in); + for (i = 0; !err && i < have_obj.nr; i++) + err |= feed_obj_to_hook("have", &have_obj, i, proc.in); + if (!err) + err |= feed_msg_to_hook(proc.in, "time %ld.%06ld\n", + (long)tv->tv_sec, (long)tv->tv_usec); + if (!err) + err |= feed_msg_to_hook(proc.in, "size %ld\n", (long)total); + if (!err) + err |= feed_msg_to_hook(proc.in, "kind %s\n", + (nr_our_refs == want_obj.nr && !have_obj.nr) + ? "clone" : "fetch"); + if (close(proc.in)) + err = 1; + if (finish_command(&proc)) + err = 1; + return err; +} + static void create_pack_file(void) { + struct timeval start_tv, tv; struct async rev_list; struct child_process pack_objects; int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); @@ -151,17 +213,27 @@ static void create_pack_file(void) char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; int buffered = -1; - ssize_t sz; + ssize_t sz, total_sz; const char *argv[10]; int arg = 0; - rev_list.proc = do_rev_list; - /* .data is just a boolean: any non-NULL value will do */ - rev_list.data = create_full_pack ? &rev_list : NULL; - if (start_async(&rev_list)) - die("git upload-pack: unable to fork git-rev-list"); + gettimeofday(&start_tv, NULL); + total_sz = 0; + if (shallow_nr) { + rev_list.proc = do_rev_list; + rev_list.data = 0; + if (start_async(&rev_list)) + die("git upload-pack: unable to fork git-rev-list"); + argv[arg++] = "pack-objects"; + } else { + argv[arg++] = "pack-objects"; + argv[arg++] = "--revs"; + if (create_full_pack) + argv[arg++] = "--all"; + else if (use_thin_pack) + argv[arg++] = "--thin"; + } - argv[arg++] = "pack-objects"; argv[arg++] = "--stdout"; if (!no_progress) argv[arg++] = "--progress"; @@ -172,7 +244,7 @@ static void create_pack_file(void) argv[arg++] = NULL; memset(&pack_objects, 0, sizeof(pack_objects)); - pack_objects.in = rev_list.out; /* start_command closes it */ + pack_objects.in = shallow_nr ? rev_list.out : -1; pack_objects.out = -1; pack_objects.err = -1; pack_objects.git_cmd = 1; @@ -181,6 +253,24 @@ static void create_pack_file(void) if (start_command(&pack_objects)) die("git upload-pack: unable to fork git-pack-objects"); + /* pass on revisions we (don't) want */ + if (!shallow_nr) { + FILE *pipe_fd = xfdopen(pack_objects.in, "w"); + if (!create_full_pack) { + int i; + for (i = 0; i < want_obj.nr; i++) + fprintf(pipe_fd, "%s\n", sha1_to_hex(want_obj.objects[i].item->sha1)); + fprintf(pipe_fd, "--not\n"); + for (i = 0; i < have_obj.nr; i++) + fprintf(pipe_fd, "%s\n", sha1_to_hex(have_obj.objects[i].item->sha1)); + } + + fprintf(pipe_fd, "\n"); + fflush(pipe_fd); + fclose(pipe_fd); + } + + /* We read from pack_objects.err to capture stderr output for * progress bar, and pack_objects.out to capture the pack data. */ @@ -237,7 +327,7 @@ static void create_pack_file(void) sz = xread(pack_objects.out, cp, sizeof(data) - outsz); if (0 < sz) - ; + total_sz += sz; else if (sz == 0) { close(pack_objects.out); pack_objects.out = -1; @@ -276,7 +366,7 @@ static void create_pack_file(void) error("git upload-pack: git-pack-objects died with error."); goto fail; } - if (finish_async(&rev_list)) + if (shallow_nr && finish_async(&rev_list)) goto fail; /* error was already reported */ /* flush the data */ @@ -289,6 +379,16 @@ static void create_pack_file(void) } if (use_sideband) packet_flush(1); + + gettimeofday(&tv, NULL); + tv.tv_sec -= start_tv.tv_sec; + if (tv.tv_usec < start_tv.tv_usec) { + tv.tv_sec--; + tv.tv_usec += 1000000; + } + tv.tv_usec -= start_tv.tv_usec; + if (run_post_upload_pack_hook(total_sz, &tv)) + warning("post-upload-hook failed"); return; fail: @@ -402,7 +502,7 @@ static int get_common_commits(void) save_commit_buffer = 0; - for(;;) { + for (;;) { int len = packet_read_line(0, line, sizeof(line)); reset_timeout(); @@ -451,8 +551,9 @@ static void receive_needs(void) static char line[1000]; int len, depth = 0; + shallow_nr = 0; if (debug_fd) - write_in_full(debug_fd, "#S\n", 3); + write_str_in_full(debug_fd, "#S\n"); for (;;) { struct object *o; unsigned char sha1_buf[20]; @@ -466,7 +567,6 @@ static void receive_needs(void) if (!prefixcmp(line, "shallow ")) { unsigned char sha1[20]; struct object *object; - use_thin_pack = 0; if (get_sha1(line + 8, sha1)) die("invalid shallow line: %s", line); object = parse_object(sha1); @@ -478,7 +578,6 @@ static void receive_needs(void) } if (!prefixcmp(line, "deepen ")) { char *end; - use_thin_pack = 0; depth = strtol(line + 7, &end, 0); if (end == line + 7 || depth <= 0) die("Invalid deepen: %s", line); @@ -520,7 +619,7 @@ static void receive_needs(void) } } if (debug_fd) - write_in_full(debug_fd, "#E\n", 3); + write_str_in_full(debug_fd, "#E\n"); if (!use_sideband && daemon_mode) no_progress = 1; @@ -538,6 +637,7 @@ static void receive_needs(void) packet_write(1, "shallow %s", sha1_to_hex(object->sha1)); register_shallow(object->sha1); + shallow_nr++; } result = result->next; } @@ -560,6 +660,7 @@ static void receive_needs(void) NULL, &want_obj); parents = parents->next; } + add_object_array(object, NULL, &extra_edge_obj); } /* make sure commit traversal conforms to client */ register_shallow(object->sha1); @@ -571,6 +672,8 @@ static void receive_needs(void) for (i = 0; i < shallows.nr; i++) register_shallow(shallows.objects[i].item->sha1); } + + shallow_nr += shallows.nr; free(shallows.objects); } @@ -622,6 +725,7 @@ int main(int argc, char **argv) int strict = 0; git_extract_argv0_path(argv[0]); + read_replace_refs = 0; for (i = 1; i < argc; i++) { char *arg = argv[i]; @@ -21,9 +21,8 @@ static struct git_var git_vars[] = { static void list_vars(void) { struct git_var *ptr; - for(ptr = git_vars; ptr->read; ptr++) { + for (ptr = git_vars; ptr->read; ptr++) printf("%s=%s\n", ptr->name, ptr->read(IDENT_WARN_ON_NO_NAME)); - } } static const char *read_var(const char *var) @@ -31,7 +30,7 @@ static const char *read_var(const char *var) struct git_var *ptr; const char *val; val = NULL; - for(ptr = git_vars; ptr->read; ptr++) { + for (ptr = git_vars; ptr->read; ptr++) { if (strcmp(var, ptr->name) == 0) { val = ptr->read(IDENT_ERROR_ON_NO_NAME); break; diff --git a/wt-status.c b/wt-status.c index 47735d812..38eb24536 100644 --- a/wt-status.c +++ b/wt-status.c @@ -1,6 +1,5 @@ #include "cache.h" #include "wt-status.h" -#include "color.h" #include "object.h" #include "dir.h" #include "commit.h" @@ -11,38 +10,18 @@ #include "run-command.h" #include "remote.h" -int wt_status_relative_paths = 1; -int wt_status_use_color = -1; -static int wt_status_submodule_summary; -static char wt_status_colors[][COLOR_MAXLEN] = { +static char default_wt_status_colors[][COLOR_MAXLEN] = { GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ GIT_COLOR_GREEN, /* WT_STATUS_UPDATED */ GIT_COLOR_RED, /* WT_STATUS_CHANGED */ GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */ GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */ + GIT_COLOR_RED, /* WT_STATUS_UNMERGED */ }; -enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - -static int parse_status_slot(const char *var, int offset) -{ - if (!strcasecmp(var+offset, "header")) - return WT_STATUS_HEADER; - if (!strcasecmp(var+offset, "updated") - || !strcasecmp(var+offset, "added")) - return WT_STATUS_UPDATED; - if (!strcasecmp(var+offset, "changed")) - return WT_STATUS_CHANGED; - if (!strcasecmp(var+offset, "untracked")) - return WT_STATUS_UNTRACKED; - if (!strcasecmp(var+offset, "nobranch")) - return WT_STATUS_NOBRANCH; - die("bad config variable '%s'", var); -} - -static const char *color(int slot) +static const char *color(int slot, struct wt_status *s) { - return wt_status_use_color > 0 ? wt_status_colors[slot] : ""; + return s->use_color > 0 ? s->color_palette[slot] : ""; } void wt_status_prepare(struct wt_status *s) @@ -51,17 +30,40 @@ void wt_status_prepare(struct wt_status *s) const char *head; memset(s, 0, sizeof(*s)); + memcpy(s->color_palette, default_wt_status_colors, + sizeof(default_wt_status_colors)); + s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + s->use_color = -1; + s->relative_paths = 1; head = resolve_ref("HEAD", sha1, 0, NULL); s->branch = head ? xstrdup(head) : NULL; s->reference = "HEAD"; s->fp = stdout; s->index_file = get_index_file(); + s->change.strdup_strings = 1; + s->untracked.strdup_strings = 1; +} + +static void wt_status_print_unmerged_header(struct wt_status *s) +{ + const char *c = color(WT_STATUS_HEADER, s); + color_fprintf_ln(s->fp, c, "# Unmerged paths:"); + if (!advice_status_hints) + return; + if (!s->is_initial) + color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); + else + color_fprintf_ln(s->fp, c, "# (use \"git rm --cached <file>...\" to unstage)"); + color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to mark resolution)"); + color_fprintf_ln(s->fp, c, "#"); } static void wt_status_print_cached_header(struct wt_status *s) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changes to be committed:"); + if (!advice_status_hints) + return; if (!s->is_initial) { color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); } else { @@ -73,8 +75,10 @@ static void wt_status_print_cached_header(struct wt_status *s) static void wt_status_print_dirty_header(struct wt_status *s, int has_deleted) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Changed but not updated:"); + if (!advice_status_hints) + return; if (!has_deleted) color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)"); else @@ -85,31 +89,73 @@ static void wt_status_print_dirty_header(struct wt_status *s, static void wt_status_print_untracked_header(struct wt_status *s) { - const char *c = color(WT_STATUS_HEADER); + const char *c = color(WT_STATUS_HEADER, s); color_fprintf_ln(s->fp, c, "# Untracked files:"); + if (!advice_status_hints) + return; color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); color_fprintf_ln(s->fp, c, "#"); } static void wt_status_print_trailer(struct wt_status *s) { - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } #define quote_path quote_path_relative -static void wt_status_print_filepair(struct wt_status *s, - int t, struct diff_filepair *p) +static void wt_status_print_unmerged_data(struct wt_status *s, + struct string_list_item *it) { - const char *c = color(t); + const char *c = color(WT_STATUS_UNMERGED, s); + struct wt_status_change_data *d = it->util; + struct strbuf onebuf = STRBUF_INIT; + const char *one, *how = "bug"; + + one = quote_path(it->string, -1, &onebuf, s->prefix); + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); + switch (d->stagemask) { + case 1: how = "both deleted:"; break; + case 2: how = "added by us:"; break; + case 3: how = "deleted by them:"; break; + case 4: how = "added by them:"; break; + case 5: how = "deleted by us:"; break; + case 6: how = "both added:"; break; + case 7: how = "both modified:"; break; + } + color_fprintf(s->fp, c, "%-20s%s\n", how, one); + strbuf_release(&onebuf); +} + +static void wt_status_print_change_data(struct wt_status *s, + int change_type, + struct string_list_item *it) +{ + struct wt_status_change_data *d = it->util; + const char *c = color(change_type, s); + int status = status; + char *one_name; + char *two_name; const char *one, *two; struct strbuf onebuf = STRBUF_INIT, twobuf = STRBUF_INIT; - one = quote_path(p->one->path, -1, &onebuf, s->prefix); - two = quote_path(p->two->path, -1, &twobuf, s->prefix); + one_name = two_name = it->string; + switch (change_type) { + case WT_STATUS_UPDATED: + status = d->index_status; + if (d->head_path) + one_name = d->head_path; + break; + case WT_STATUS_CHANGED: + status = d->worktree_status; + break; + } + + one = quote_path(one_name, -1, &onebuf, s->prefix); + two = quote_path(two_name, -1, &twobuf, s->prefix); - color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); - switch (p->status) { + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); + switch (status) { case DIFF_STATUS_ADDED: color_fprintf(s->fp, c, "new file: %s", one); break; @@ -135,64 +181,114 @@ static void wt_status_print_filepair(struct wt_status *s, color_fprintf(s->fp, c, "unmerged: %s", one); break; default: - die("bug: unhandled diff status %c", p->status); + die("bug: unhandled diff status %c", status); } fprintf(s->fp, "\n"); strbuf_release(&onebuf); strbuf_release(&twobuf); } -static void wt_status_print_updated_cb(struct diff_queue_struct *q, - struct diff_options *options, - void *data) +static void wt_status_collect_changed_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) { struct wt_status *s = data; - int shown_header = 0; int i; + + if (!q->nr) + return; + s->workdir_dirty = 1; for (i = 0; i < q->nr; i++) { - if (q->queue[i]->status == 'U') - continue; - if (!shown_header) { - wt_status_print_cached_header(s); - s->commitable = 1; - shown_header = 1; + struct diff_filepair *p; + struct string_list_item *it; + struct wt_status_change_data *d; + + p = q->queue[i]; + it = string_list_insert(p->one->path, &s->change); + d = it->util; + if (!d) { + d = xcalloc(1, sizeof(*d)); + it->util = d; } - wt_status_print_filepair(s, WT_STATUS_UPDATED, q->queue[i]); + if (!d->worktree_status) + d->worktree_status = p->status; } - if (shown_header) - wt_status_print_trailer(s); } -static void wt_status_print_changed_cb(struct diff_queue_struct *q, - struct diff_options *options, - void *data) +static int unmerged_mask(const char *path) +{ + int pos, mask; + struct cache_entry *ce; + + pos = cache_name_pos(path, strlen(path)); + if (0 <= pos) + return 0; + + mask = 0; + pos = -pos-1; + while (pos < active_nr) { + ce = active_cache[pos++]; + if (strcmp(ce->name, path) || !ce_stage(ce)) + break; + mask |= (1 << (ce_stage(ce) - 1)); + } + return mask; +} + +static void wt_status_collect_updated_cb(struct diff_queue_struct *q, + struct diff_options *options, + void *data) { struct wt_status *s = data; int i; - if (q->nr) { - int has_deleted = 0; - s->workdir_dirty = 1; - for (i = 0; i < q->nr; i++) - if (q->queue[i]->status == DIFF_STATUS_DELETED) { - has_deleted = 1; - break; - } - wt_status_print_dirty_header(s, has_deleted); + + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p; + struct string_list_item *it; + struct wt_status_change_data *d; + + p = q->queue[i]; + it = string_list_insert(p->two->path, &s->change); + d = it->util; + if (!d) { + d = xcalloc(1, sizeof(*d)); + it->util = d; + } + if (!d->index_status) + d->index_status = p->status; + switch (p->status) { + case DIFF_STATUS_COPIED: + case DIFF_STATUS_RENAMED: + d->head_path = xstrdup(p->one->path); + break; + case DIFF_STATUS_UNMERGED: + d->stagemask = unmerged_mask(p->two->path); + break; + } } - for (i = 0; i < q->nr; i++) - wt_status_print_filepair(s, WT_STATUS_CHANGED, q->queue[i]); - if (q->nr) - wt_status_print_trailer(s); } -static void wt_status_print_updated(struct wt_status *s) +static void wt_status_collect_changes_worktree(struct wt_status *s) { struct rev_info rev; + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, NULL); + rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = wt_status_collect_changed_cb; + rev.diffopt.format_callback_data = s; + run_diff_files(&rev, 0); +} + +static void wt_status_collect_changes_index(struct wt_status *s) +{ + struct rev_info rev; + init_revisions(&rev, NULL); setup_revisions(0, NULL, &rev, s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference); rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = wt_status_print_updated_cb; + rev.diffopt.format_callback = wt_status_collect_updated_cb; rev.diffopt.format_callback_data = s; rev.diffopt.detect_rename = 1; rev.diffopt.rename_limit = 200; @@ -200,15 +296,155 @@ static void wt_status_print_updated(struct wt_status *s) run_diff_index(&rev, 1); } +static void wt_status_collect_changes_initial(struct wt_status *s) +{ + int i; + + for (i = 0; i < active_nr; i++) { + struct string_list_item *it; + struct wt_status_change_data *d; + struct cache_entry *ce = active_cache[i]; + + it = string_list_insert(ce->name, &s->change); + d = it->util; + if (!d) { + d = xcalloc(1, sizeof(*d)); + it->util = d; + } + if (ce_stage(ce)) { + d->index_status = DIFF_STATUS_UNMERGED; + d->stagemask |= (1 << (ce_stage(ce) - 1)); + } + else + d->index_status = DIFF_STATUS_ADDED; + } +} + +static void wt_status_collect_untracked(struct wt_status *s) +{ + int i; + struct dir_struct dir; + + if (!s->show_untracked_files) + return; + memset(&dir, 0, sizeof(dir)); + if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES) + dir.flags |= + DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; + setup_standard_excludes(&dir); + + fill_directory(&dir, NULL); + for (i = 0; i < dir.nr; i++) { + struct dir_entry *ent = dir.entries[i]; + if (!cache_name_is_other(ent->name, ent->len)) + continue; + s->workdir_untracked = 1; + string_list_insert(ent->name, &s->untracked); + } +} + +void wt_status_collect(struct wt_status *s) +{ + wt_status_collect_changes_worktree(s); + + if (s->is_initial) + wt_status_collect_changes_initial(s); + else + wt_status_collect_changes_index(s); + wt_status_collect_untracked(s); +} + +static void wt_status_print_unmerged(struct wt_status *s) +{ + int shown_header = 0; + int i; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + it = &(s->change.items[i]); + d = it->util; + if (!d->stagemask) + continue; + if (!shown_header) { + wt_status_print_unmerged_header(s); + shown_header = 1; + } + wt_status_print_unmerged_data(s, it); + } + if (shown_header) + wt_status_print_trailer(s); + +} + +static void wt_status_print_updated(struct wt_status *s) +{ + int shown_header = 0; + int i; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + it = &(s->change.items[i]); + d = it->util; + if (!d->index_status || + d->index_status == DIFF_STATUS_UNMERGED) + continue; + if (!shown_header) { + wt_status_print_cached_header(s); + s->commitable = 1; + shown_header = 1; + } + wt_status_print_change_data(s, WT_STATUS_UPDATED, it); + } + if (shown_header) + wt_status_print_trailer(s); +} + +/* + * -1 : has delete + * 0 : no change + * 1 : some change but no delete + */ +static int wt_status_check_worktree_changes(struct wt_status *s) +{ + int i; + int changes = 0; + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + d = s->change.items[i].util; + if (!d->worktree_status || + d->worktree_status == DIFF_STATUS_UNMERGED) + continue; + changes = 1; + if (d->worktree_status == DIFF_STATUS_DELETED) + return -1; + } + return changes; +} + static void wt_status_print_changed(struct wt_status *s) { - struct rev_info rev; - init_revisions(&rev, ""); - setup_revisions(0, NULL, &rev, NULL); - rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK; - rev.diffopt.format_callback = wt_status_print_changed_cb; - rev.diffopt.format_callback_data = s; - run_diff_files(&rev, 0); + int i; + int worktree_changes = wt_status_check_worktree_changes(s); + + if (!worktree_changes) + return; + + wt_status_print_dirty_header(s, worktree_changes < 0); + + for (i = 0; i < s->change.nr; i++) { + struct wt_status_change_data *d; + struct string_list_item *it; + it = &(s->change.items[i]); + d = it->util; + if (!d->worktree_status || + d->worktree_status == DIFF_STATUS_UNMERGED) + continue; + wt_status_print_change_data(s, WT_STATUS_CHANGED, it); + } + wt_status_print_trailer(s); } static void wt_status_print_submodule_summary(struct wt_status *s) @@ -228,7 +464,7 @@ static void wt_status_print_submodule_summary(struct wt_status *s) NULL }; - sprintf(summary_limit, "%d", wt_status_submodule_summary); + sprintf(summary_limit, "%d", s->submodule_summary); snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", s->index_file); memset(&sm_summary, 0, sizeof(sm_summary)); @@ -243,32 +479,20 @@ static void wt_status_print_submodule_summary(struct wt_status *s) static void wt_status_print_untracked(struct wt_status *s) { - struct dir_struct dir; int i; - int shown_header = 0; struct strbuf buf = STRBUF_INIT; - memset(&dir, 0, sizeof(dir)); - - if (!s->untracked) - dir.flags |= - DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; - setup_standard_excludes(&dir); + if (!s->untracked.nr) + return; - fill_directory(&dir, NULL); - for(i = 0; i < dir.nr; i++) { - struct dir_entry *ent = dir.entries[i]; - if (!cache_name_is_other(ent->name, ent->len)) - continue; - if (!shown_header) { - s->workdir_untracked = 1; - wt_status_print_untracked_header(s); - shown_header = 1; - } - color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); - color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED), "%s", - quote_path(ent->name, ent->len, - &buf, s->prefix)); + wt_status_print_untracked_header(s); + for (i = 0; i < s->untracked.nr; i++) { + struct string_list_item *it; + it = &(s->untracked.items[i]); + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "#\t"); + color_fprintf_ln(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", + quote_path(it->string, strlen(it->string), + &buf, s->prefix)); } strbuf_release(&buf); } @@ -310,15 +534,15 @@ static void wt_status_print_tracking(struct wt_status *s) return; for (cp = sb.buf; (ep = strchr(cp, '\n')) != NULL; cp = ep + 1) - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# %.*s", (int)(ep - cp), cp); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } void wt_status_print(struct wt_status *s) { unsigned char sha1[20]; - const char *branch_color = color(WT_STATUS_HEADER); + const char *branch_color = color(WT_STATUS_HEADER, s); s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0; if (s->branch) { @@ -328,26 +552,29 @@ void wt_status_print(struct wt_status *s) branch_name += 11; else if (!strcmp(branch_name, "HEAD")) { branch_name = ""; - branch_color = color(WT_STATUS_NOBRANCH); + branch_color = color(WT_STATUS_NOBRANCH, s); on_what = "Not currently on any branch."; } - color_fprintf(s->fp, color(WT_STATUS_HEADER), "# "); + color_fprintf(s->fp, color(WT_STATUS_HEADER, s), "# "); color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name); if (!s->is_initial) wt_status_print_tracking(s); } + wt_status_collect(s); + if (s->is_initial) { - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "# Initial commit"); - color_fprintf_ln(s->fp, color(WT_STATUS_HEADER), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "# Initial commit"); + color_fprintf_ln(s->fp, color(WT_STATUS_HEADER, s), "#"); } wt_status_print_updated(s); + wt_status_print_unmerged(s); wt_status_print_changed(s); - if (wt_status_submodule_summary) + if (s->submodule_summary) wt_status_print_submodule_summary(s); - if (show_untracked_files) + if (s->show_untracked_files) wt_status_print_untracked(s); else if (s->commitable) fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); @@ -361,53 +588,13 @@ void wt_status_print(struct wt_status *s) ; /* nothing */ else if (s->workdir_dirty) printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); - else if (s->workdir_untracked) + else if (s->untracked.nr) printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); else if (s->is_initial) printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); - else if (!show_untracked_files) + else if (!s->show_untracked_files) printf("nothing to commit (use -u to show untracked files)\n"); else printf("nothing to commit (working directory clean)\n"); } } - -int git_status_config(const char *k, const char *v, void *cb) -{ - if (!strcmp(k, "status.submodulesummary")) { - int is_bool; - wt_status_submodule_summary = git_config_bool_or_int(k, v, &is_bool); - if (is_bool && wt_status_submodule_summary) - wt_status_submodule_summary = -1; - return 0; - } - if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { - wt_status_use_color = git_config_colorbool(k, v, -1); - return 0; - } - if (!prefixcmp(k, "status.color.") || !prefixcmp(k, "color.status.")) { - int slot = parse_status_slot(k, 13); - if (!v) - return config_error_nonbool(k); - color_parse(v, k, wt_status_colors[slot]); - return 0; - } - if (!strcmp(k, "status.relativepaths")) { - wt_status_relative_paths = git_config_bool(k, v); - return 0; - } - if (!strcmp(k, "status.showuntrackedfiles")) { - if (!v) - return config_error_nonbool(k); - else if (!strcmp(v, "no")) - show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(v, "normal")) - show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(v, "all")) - show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - return error("Invalid untracked files mode '%s'", v); - return 0; - } - return git_diff_ui_config(k, v, cb); -} diff --git a/wt-status.h b/wt-status.h index 78add09bd..a0e75177b 100644 --- a/wt-status.h +++ b/wt-status.h @@ -2,13 +2,16 @@ #define STATUS_H #include <stdio.h> +#include "string-list.h" +#include "color.h" enum color_wt_status { - WT_STATUS_HEADER, + WT_STATUS_HEADER = 0, WT_STATUS_UPDATED, WT_STATUS_CHANGED, WT_STATUS_UNTRACKED, WT_STATUS_NOBRANCH, + WT_STATUS_UNMERGED, }; enum untracked_status_type { @@ -16,7 +19,13 @@ enum untracked_status_type { SHOW_NORMAL_UNTRACKED_FILES, SHOW_ALL_UNTRACKED_FILES }; -extern enum untracked_status_type show_untracked_files; + +struct wt_status_change_data { + int worktree_status; + int index_status; + int stagemask; + char *head_path; +}; struct wt_status { int is_initial; @@ -24,8 +33,13 @@ struct wt_status { const char *reference; int verbose; int amend; - int untracked; int nowarn; + int use_color; + int relative_paths; + int submodule_summary; + enum untracked_status_type show_untracked_files; + char color_palette[WT_STATUS_UNMERGED+1][COLOR_MAXLEN]; + /* These are computed during processing of the individual sections */ int commitable; int workdir_dirty; @@ -33,12 +47,12 @@ struct wt_status { const char *index_file; FILE *fp; const char *prefix; + struct string_list change; + struct string_list untracked; }; -int git_status_config(const char *var, const char *value, void *cb); -extern int wt_status_use_color; -extern int wt_status_relative_paths; void wt_status_prepare(struct wt_status *s); void wt_status_print(struct wt_status *s); +void wt_status_collect(struct wt_status *s); #endif /* STATUS_H */ |