diff options
196 files changed, 4805 insertions, 1456 deletions
diff --git a/.gitignore b/.gitignore index 41c0b20a76..10808e3a73 100644 --- a/.gitignore +++ b/.gitignore @@ -105,6 +105,7 @@ git-reflog git-relink git-remote git-repack +git-replace git-repo-config git-request-pull git-rerere diff --git a/Documentation/RelNotes-1.6.4.1.txt b/Documentation/RelNotes-1.6.4.1.txt new file mode 100644 index 0000000000..e439e45b96 --- /dev/null +++ b/Documentation/RelNotes-1.6.4.1.txt @@ -0,0 +1,46 @@ +GIT v1.6.4.1 Release Notes +========================== + +Fixes since v1.6.4 +------------------ + + * An unquoted value in the configuration file, when it contains more than + one whitespaces in a row, got them replaced with a single space. + + * "git am" used to accept a single piece of e-mail per file (not a mbox) + as its input, but multiple input format support in v1.6.4 broke it. + Apparently many people have been depending on this feature. + + * The short help text for "git filter-branch" command was a single long + line, wrapped by terminals, and was hard to read. + + * The "recursive" strategy of "git merge" segfaulted when a merge has + more than one merge-bases, and merging of these merge-bases involves + a rename/rename or a rename/add conflict. + + * "git pull --rebase" did not use the right fork point when the + repository has already fetched from the upstream that rewinds the + branch it is based on in an earlier fetch. + + * Explain the concept of fast-forward more fully in "git push" + documentation, and hint to refer to it from an error message when the + command refuses an update to protect the user. + + * The default value for pack.deltacachesize, used by "git repack", is now + 256M, instead of unbounded. Otherwise a repack of a moderately sized + repository would needlessly eat into swap. + + * Document how "git repack" (hence "git gc") interacts with a repository + that borrows its objects from other repositories (e.g. ones created by + "git clone -s"). + + * "git show" on an annotated tag lacked a delimiting blank line between + the tag itself and the contents of the object it tags. + + * "git verify-pack -v" erroneously reported number of objects with too + deep delta depths as "chain length 0" objects. + + * Long names of authors and committers outside US-ASCII were sometimes + incorrectly shown in "gitweb". + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.4.2.txt b/Documentation/RelNotes-1.6.4.2.txt new file mode 100644 index 0000000000..c11ec0115c --- /dev/null +++ b/Documentation/RelNotes-1.6.4.2.txt @@ -0,0 +1,32 @@ +GIT v1.6.4.2 Release Notes +========================== + +Fixes since v1.6.4.1 +-------------------- + +* --date=relative output between 1 and 5 years ago rounded the number of + years when saying X years Y months ago, instead of rounding it down. + +* "git add -p" did not handle changes in executable bits correctly + (a regression around 1.6.3). + +* "git apply" did not honor GNU diff's convention to mark the creation/deletion + event with UNIX epoch timestamp on missing side. + +* "git checkout" incorrectly removed files in a directory pointed by a + symbolic link during a branch switch that replaces a directory with + a symbolic link. + +* "git clean -d -f" happily descended into a subdirectory that is managed by a + separate git repository. It now requires two -f options for safety. + +* "git fetch/push" over http transports had two rather grave bugs. + +* "git format-patch --cover-letter" did not prepare the cover letter file + for use with non-ASCII strings when there are the series contributors with + non-ASCII names. + +* "git pull origin branch" and "git fetch origin && git merge origin/branch" + left different merge messages in the resulting commit. + +Other minor documentation updates are included. diff --git a/Documentation/RelNotes-1.6.5.txt b/Documentation/RelNotes-1.6.5.txt index 856047d16f..84a84519d4 100644 --- a/Documentation/RelNotes-1.6.5.txt +++ b/Documentation/RelNotes-1.6.5.txt @@ -31,14 +31,68 @@ Updates since v1.6.4 (subsystems) + * various updates to git-svn and gitweb. + (portability) + * more improvements on mingw port. + (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. + (usability, bells and whistles) + * 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 commit --dry-run $args" is a new recommended way to ask "what would + happen if I try to commit with these arguments." + + * "git cvsimport" now supports password-protected pserver access. + + * "git fast-export" learned --no-data option that can be useful when + reordering commits and trees without touching the contents of + blobs. + + * "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 push" can be told to be --quiet. + + * informational output from "git reset" that lists the locally modified + paths is made consistent with that of "git checkout $another_branch". + + * "git status" gives more descriptive output for unmerged paths. + + * "git submodule" learned to give submodule name to scripts run with + "foreach" subcommand. + + * 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. + (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 ------------------ @@ -48,4 +102,8 @@ Fixes since v1.6.4 # 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.1-266-g235db15 +echo O=$(git describe master) +git shortlog --no-merges $O..master --not maint diff --git a/Documentation/config.txt b/Documentation/config.txt index c6f09f801a..5256c7fb81 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -461,6 +461,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]. @@ -605,7 +613,7 @@ color.interactive.<slot>:: Use customized color for 'git-add --interactive' output. `<slot>` may be `prompt`, `header`, `help` or `error`, for four distinct types of normal output from interactive - programs. The values of these variables may be specified as + commands. The values of these variables may be specified as in color.branch.<slot>. color.pager:: @@ -1113,7 +1121,7 @@ instaweb.port:: linkgit:git-instaweb[1]. interactive.singlekey:: - In interactive programs, allow the user to provide one-letter + In interactive commands, allow the user to provide one-letter input with a single key (i.e., without hitting enter). Currently this is used only by the `\--patch` mode of linkgit:git-add[1]. Note that this setting is silently @@ -1218,12 +1226,20 @@ pack.compression:: pack.deltaCacheSize:: The maximum memory in bytes used for caching deltas in - linkgit:git-pack-objects[1]. - A value of 0 means no limit. Defaults to 0. + linkgit:git-pack-objects[1] before writing them out to a pack. + This cache is used to speed up the writing object phase by not + having to recompute the final delta result once the best match + for all objects is found. Repacking large repositories on machines + which are tight with memory might be badly impacted by this though, + especially if this cache pushes the system into swapping. + A value of 0 means no limit. The smallest size of 1 byte may be + used to virtually disable this cache. Defaults to 256 MiB. pack.deltaCacheLimit:: The maximum size of a delta, that is cached in - linkgit:git-pack-objects[1]. Defaults to 1000. + linkgit:git-pack-objects[1]. This cache is used to speed up the + writing object phase by not having to recompute the final delta + result once the best match for all objects is found. Defaults to 1000. pack.threads:: Specifies the number of threads to spawn when searching for best diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index d313795fdb..ea3b1bc19f 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -1,7 +1,7 @@ -q:: --quiet:: Pass --quiet to git-fetch-pack and silence any other internally - used programs. + used git commands. -v:: --verbose:: diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index ab1943c712..e67b7e875e 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -10,7 +10,7 @@ SYNOPSIS [verse] 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p] [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N] - [--refresh] [--ignore-errors] [--] <filepattern>... + [--refresh] [--ignore-errors] [--] [<filepattern>...] DESCRIPTION ----------- diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 32e689b2bf..fcacc94650 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -11,7 +11,7 @@ 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] [<mbox> | <Maildir>...] @@ -65,6 +65,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>:: diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 735374d7df..5ee8c91f2d 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 bc132c87e1..92444ddf10 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -9,7 +9,7 @@ git-archive - Create an archive of files from a named tree SYNOPSIS -------- [verse] -'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>] +'git archive' [--format=<fmt>] [--list] [--prefix=<prefix>/] [<extra>] [--output=<file>] [--worktree-attributes] [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish> [path...] diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index ae201deb7a..aad71dc59a 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 ad4b31e892..b1314b5614 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -45,9 +45,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. diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index be894af39f..9d291bdd26 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -27,8 +27,12 @@ OPTIONS ------- -d:: Remove untracked directories in addition to untracked files. + If an untracked directory is managed by a different git + repository, it is not removed by default. Use -f option twice + 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 b14de6c407..88ea272ee5 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 ----------- @@ -72,8 +72,16 @@ These objects may be removed by normal git operations (such as 'git-commit') which automatically call `git gc --auto`. (See linkgit:git-gc[1].) If these objects are removed and were referenced by the cloned repository, then the cloned repository will become corrupt. - - ++ +Note that running `git repack` without the `-l` option in a repository +cloned with `-s` will copy objects from the source repository into a pack +in the cloned repository, removing the disk space savings of `clone -s`. +It is safe, however, to run `git gc`, which uses the `-l` option by +default. ++ +If you want to break the dependency of a repository cloned with `-s` on +its source repository, you can simply run `git repack -a` to copy all +objects from the source repository into a pack in the cloned repository. --reference <repository>:: If the reference repository is on the local machine @@ -139,6 +147,14 @@ then the cloned repository will become corrupt. 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 b5d81be7ec..64f94cfe12 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -8,8 +8,8 @@ git-commit - Record changes to the repository SYNOPSIS -------- [verse] -'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] - [(-c | -C) <commit>] [-F <file> | -m <msg>] +'git commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [--dry-run] + [(-c | -C) <commit>] [-F <file> | -m <msg>] [--dry-run] [--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'. @@ -70,6 +69,12 @@ OPTIONS Like '-C', but with '-c' the editor is invoked, so that the user can further edit the commit message. +--dry-run:: + Do not actually make a commit, but show the list of paths + with updates in the index, paths with changes in the work tree, + and paths that are untracked, similar to the one that is given + in the commit log editor. + -F <file>:: --file=<file>:: Take the commit message from the given file. Use '-' to @@ -198,6 +203,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-filter-branch.txt b/Documentation/git-filter-branch.txt index ab527b5b31..32ea8564a5 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -305,6 +305,16 @@ range in addition to the new branch name. The new branch name will point to the top-most revision that a 'git-rev-list' of this range will print. +If you need to add 'Acked-by' lines to, say, the last 10 commits (none +of which is a merge), use this command: + +-------------------------------------------------------- +git filter-branch --msg-filter ' + cat && + echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>" +' HEAD~10..HEAD +-------------------------------------------------------- + *NOTE* the changes introduced by the commits, and which are not reverted by subsequent commits, will still be in the rewritten branch. If you want to throw out _changes_ together with the commits, you should use the diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index b292e9843a..dcac8c8e29 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -61,7 +61,7 @@ automatic consolidation of packs. --prune=<date>:: Prune loose objects older than date (default is 2 weeks ago, - overrideable by the config variable `gc.pruneExpire`). This + overridable by the config variable `gc.pruneExpire`). This option is on by default. --no-prune:: diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index b753c9d76f..8c700200f5 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-instaweb.txt b/Documentation/git-instaweb.txt index 22da21a54f..0771f25443 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-log.txt b/Documentation/git-log.txt index 34cf4e5811..3d79de11ec 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -37,8 +37,12 @@ include::diff-options.txt[] and <until>, see "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. ---decorate:: - Print out the ref names of any commits that are shown. +--decorate[=short|full]:: + Print out the ref names of any commits that are shown. If 'short' is + specified, the ref name prefixes 'refs/heads/', 'refs/tags/' and + 'refs/remotes/' will not be printed. If 'full' is specified, the + full ref name (including prefix) will be printed. The default option + is 'short'. --source:: Print out the ref name given on the command line by which each diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 057a021eb5..021066e95d 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-merge.txt b/Documentation/git-merge.txt index c04ae739ed..af68d694a0 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]... - [-m <msg>] <remote> <remote>... + [-m <msg>] <remote>... 'git merge' <msg> HEAD <remote>... DESCRIPTION diff --git a/Documentation/git-mv.txt b/Documentation/git-mv.txt index 9c5660275b..bdcb58526e 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-push.txt b/Documentation/git-push.txt index 2653388fd8..58d2bd5d4a 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -195,6 +195,92 @@ reason:: refs, no explanation is needed. For a failed ref, the reason for failure is described. +Note about fast-forwards +------------------------ + +When an update changes a branch (or more in general, a ref) that used to +point at commit A to point at another commit B, it is called a +fast-forward update if and only if B is a descendant of A. + +In a fast-forward update from A to B, the set of commits that the original +commit A built on top of is a subset of the commits the new commit B +builds on top of. Hence, it does not lose any history. + +In contrast, a non-fast-forward update will lose history. For example, +suppose you and somebody else started at the same commit X, and you built +a history leading to commit B while the other person built a history +leading to commit A. The history looks like this: + +---------------- + + B + / + ---X---A + +---------------- + +Further suppose that the other person already pushed changes leading to A +back to the original repository you two obtained the original commit X. + +The push done by the other person updated the branch that used to point at +commit X to point at commit A. It is a fast-forward. + +But if you try to push, you will attempt to update the branch (that +now points at A) with commit B. This does _not_ fast-forward. If you did +so, the changes introduced by commit A will be lost, because everybody +will now start building on top of B. + +The command by default does not allow an update that is not a fast-forward +to prevent such loss of history. + +If you do not want to lose your work (history from X to B) nor the work by +the other person (history from X to A), you would need to first fetch the +history from the repository, create a history that contains changes done +by both parties, and push the result back. + +You can perform "git pull", resolve potential conflicts, and "git push" +the result. A "git pull" will create a merge commit C between commits A +and B. + +---------------- + + B---C + / / + ---X---A + +---------------- + +Updating A with the resulting merge commit will fast-forward and your +push will be accepted. + +Alternatively, you can rebase your change between X and B on top of A, +with "git pull --rebase", and push the result back. The rebase will +create a new commit D that builds the change between X and B on top of +A. + +---------------- + + B D + / / + ---X---A + +---------------- + +Again, updating A with this commit will fast-forward and your push will be +accepted. + +There is another common situation where you may encounter non-fast-forward +rejection when you try to push, and it is possible even when you are +pushing into a repository nobody else pushes into. After you push commit +A yourself (in the first picture in this section), replace it with "git +commit --amend" to produce commit B, and you try to push it out, because +forgot that you have pushed A out already. In such a case, and only if +you are certain that nobody in the meantime fetched your earlier commit A +(and started building on top of it), you can run "git push --force" to +overwrite it. In other words, "git push --force" is a method reserved for +a case where you do mean to lose history. + + Examples -------- diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index db1b71d248..0aefc34d0d 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.txt b/Documentation/git-remote.txt index 9e2b4eaa38..82a3d29673 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -114,14 +114,14 @@ These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in "remotes/<name>". + -With `--dry-run` option, report what branches will be pruned, but do no +With `--dry-run` option, report what branches will be pruned, but do not actually prune them. 'update':: Fetch updates for a named set of remotes in the repository as defined by remotes.<group>. If a named group is not specified on the command line, -the configuration parameter remotes.default will get used; if +the configuration parameter remotes.default will be used; if remotes.default is not defined, all remotes which do not have the configuration parameter remote.<name>.skipDefaultUpdate set to true will be updated. (See linkgit:git-config[1]). diff --git a/Documentation/git-replace.txt b/Documentation/git-replace.txt new file mode 100644 index 0000000000..915cb77b29 --- /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-rev-list.txt b/Documentation/git-rev-list.txt index a765cfa4d2..3341d1b62f 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 @@ -84,7 +90,7 @@ between the two operands. The following two commands are equivalent: $ git rev-list A...B ----------------------------------------------------------------------- -'git-rev-list' is a very essential git program, since it +'rev-list' is a very essential git command, since it provides the ability to build and traverse commit ancestry graphs. For this reason, it has a lot of different options that enables it to be used by commands as different as 'git-bisect' and diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 1c64a02fe5..2f5ca7b1a3 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -114,7 +114,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 +218,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 7dd73ae14e..5ccdd18c89 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 22a0389f1e..1812890a7e 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-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt index 210fde03a1..6392538807 100644 --- a/Documentation/git-symbolic-ref.txt +++ b/Documentation/git-symbolic-ref.txt @@ -14,9 +14,9 @@ DESCRIPTION Given one argument, reads which branch head the given symbolic ref refers to and outputs its path, relative to the `.git/` directory. Typically you would give `HEAD` as the <name> -argument to see on which branch your working tree is on. +argument to see which branch your working tree is on. -Give two arguments, create or update a symbolic ref <name> to +Given two arguments, creates or updates a symbolic ref <name> to point at the given branch <ref>. A symbolic ref is a regular file that stores a string that diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index fa733214ab..299b04f726 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-verify-pack.txt b/Documentation/git-verify-pack.txt index d791a80819..97f7f9165e 100644 --- a/Documentation/git-verify-pack.txt +++ b/Documentation/git-verify-pack.txt @@ -25,7 +25,13 @@ OPTIONS -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/git-write-tree.txt b/Documentation/git-write-tree.txt index 26d3850e73..c8899d528a 100644 --- a/Documentation/git-write-tree.txt +++ b/Documentation/git-write-tree.txt @@ -12,7 +12,8 @@ SYNOPSIS DESCRIPTION ----------- -Creates a tree object using the current index. +Creates a tree object using the current index. The name of the new +tree object is printed to standard output. The index must be in a fully merged state. diff --git a/Documentation/git.txt b/Documentation/git.txt index 5fd5953e29..ad44cac71d 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,11 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.4/git.html[documentation for release 1.6.4] +* link:v1.6.4.2/git.html[documentation for release 1.6.4.2] * release notes for + link:RelNotes-1.6.4.2.txt[1.6.4.2], + link:RelNotes-1.6.4.1.txt[1.6.4.1], link:RelNotes-1.6.4.txt[1.6.4]. * link:v1.6.3.4/git.html[documentation for release 1.6.3.4] @@ -327,7 +329,7 @@ Synching repositories include::cmds-synchingrepositories.txt[] -The following are helper programs used by the above; end users +The following are helper commands used by the above; end users typically do not use them directly. include::cmds-synchelpers.txt[] diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index aaa073efc8..1195e83b6e 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -404,7 +404,7 @@ Performing a three-way merge The attribute `merge` affects how three versions of a file is merged when a file-level merge is necessary during `git merge`, -and other programs such as `git revert` and `git cherry-pick`. +and other commands such as `git revert` and `git cherry-pick`. Set:: diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index 7ba5e589d7..b3640c4e64 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -12,7 +12,7 @@ git * DESCRIPTION ----------- -This tutorial explains how to use the "core" git programs to set up and +This tutorial explains how to use the "core" git commands to set up and work with a git repository. If you just need to use git as a revision control system you may prefer @@ -1328,7 +1328,7 @@ into it later. Obviously, this repository creation needs to be done only once. [NOTE] -'git-push' uses a pair of programs, +'git-push' uses a pair of commands, 'git-send-pack' on your local machine, and 'git-receive-pack' on the remote machine. The communication between the two over the network internally uses an SSH connection. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 2efe7a40be..b26c28133c 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/user-manual.txt b/Documentation/user-manual.txt index 0b88a51d0b..67ebffa568 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -4131,7 +4131,7 @@ What does this mean? `git rev-list` is the original version of the revision walker, which _always_ printed a list of revisions to stdout. It is still functional, -and needs to, since most new Git programs start out as scripts using +and needs to, since most new Git commands start out as scripts using `git rev-list`. `git rev-parse` is not as important any more; it was only used to filter out @@ -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,17 +84,13 @@ 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_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). @@ -361,7 +357,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 @@ -532,6 +527,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 @@ -621,6 +617,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 +635,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 @@ -751,9 +749,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 +917,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 @@ -1018,7 +1009,7 @@ ifndef NO_OPENSSL endif else BASIC_CFLAGS += -DNO_OPENSSL - MOZILLA_SHA1 = 1 + BLK_SHA1 = 1 OPENSSL_LIBSSL = endif ifdef NEEDS_SSL_WITH_CRYPTO @@ -1167,23 +1158,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 @@ -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/arm/sha1.c b/arm/sha1.c deleted file mode 100644 index c61ad4aff9..0000000000 --- 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 b61b618486..0000000000 --- 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 41e92636e0..0000000000 --- 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 @@ -991,7 +991,7 @@ int bisect_next_all(const char *prefix) if (!hashcmp(bisect_rev, current_bad_sha1)) { exit_if_skipped_commits(tried, current_bad_sha1); - printf("%s is first bad commit\n", bisect_rev_hex); + printf("%s is the first bad commit\n", bisect_rev_hex); show_diff_tree(prefix, revs.commits->item); /* This means the bisection process succeeded. */ exit(10); diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c new file mode 100644 index 0000000000..d8934757a5 --- /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 0000000000..b864df623e --- /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 581a2a1748..006fd08769 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]); diff --git a/builtin-apply.c b/builtin-apply.c index 39dc96ae02..ae11b41ef2 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,58 @@ 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; + size_t imglen[preimage->nr]; + for (i = 0; i < preimage->nr; i++) { + size_t prelen = preimage->line[i].len; + + imglen[i] = img->line[try_lno+i].len; + if (!fuzzy_matchlines( + img->buf + try + imgoff, imglen[i], + preimage->buf + preoff, prelen)) + return 0; + if (preimage->line[i].flag & LINE_COMMON) + postlen += imglen[i] - prelen; + imgoff += imglen[i]; + 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 = imglen[i]; + + /* + * 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 +1969,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 +3411,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 +3449,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 +3535,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 +3565,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-branch.c b/builtin-branch.c index 1a03d5f356..9f57992062 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 8a9a474218..36e2116ea2 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -402,7 +402,9 @@ static int merge_working_tree(struct checkout_opts *opts, topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->flags |= DIR_SHOW_IGNORED; topts.dir->exclude_per_dir = ".gitignore"; - tree = parse_tree_indirect(old->commit->object.sha1); + tree = parse_tree_indirect(old->commit ? + old->commit->object.sha1 : + (unsigned char *)EMPTY_TREE_SHA1_BIN); init_tree_desc(&trees[0], tree->buffer, tree->size); tree = parse_tree_indirect(new->commit->object.sha1); init_tree_desc(&trees[1], tree->buffer, tree->size); @@ -541,14 +543,6 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) parse_commit(new->commit); } - if (!old.commit && !opts->force) { - if (!opts->quiet) { - warning("You appear to be on a branch yet to be born."); - warning("Forcing checkout of %s.", new->name); - } - opts->force = 1; - } - ret = merge_working_tree(opts, &old, new); if (ret) return ret; @@ -590,7 +584,7 @@ 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)"), diff --git a/builtin-clean.c b/builtin-clean.c index 2d8c735d48..28cdcd0274 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -31,6 +31,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) int i; int show_only = 0, remove_directories = 0, quiet = 0, ignored = 0; int ignored_only = 0, baselen = 0, config_set = 0, errors = 0; + int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf directory = STRBUF_INIT; struct dir_struct dir; static const char **pathspec; @@ -40,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"), @@ -69,6 +70,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) die("clean.requireForce%s set and -n or -f not given; " "refusing to clean", config_set ? "" : " not"); + if (force > 1) + rm_flags = 0; + dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; if (!ignored) @@ -131,7 +135,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) (matches == MATCHED_EXACTLY)) { if (!quiet) printf("Removing %s\n", qname); - if (remove_dir_recursively(&directory, 0) != 0) { + if (remove_dir_recursively(&directory, + rm_flags) != 0) { warning("failed to remove '%s'", qname); errors++; } diff --git a/builtin-clone.c b/builtin-clone.c index 32dea74d78..0d2b4a8200 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -38,7 +38,7 @@ 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_upload_pack = "git-upload-pack"; @@ -59,6 +59,8 @@ 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", @@ -73,6 +75,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", "" }; @@ -608,6 +614,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 4bcce06fbf..200ffdaad4 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 7a662980d1..df67a733ae 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 2e51f408f9..ffcdd055ca 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-fsck.c b/builtin-fsck.c index b3d38fa277..c58b0e337e 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 f477659100..ad0e0a5385 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -53,25 +53,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; @@ -421,7 +453,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] == '-') { @@ -478,7 +510,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 @@ -538,7 +570,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); @@ -692,6 +724,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), @@ -768,6 +803,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-log.c b/builtin-log.c index 30358166e6..25e21ed415 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -27,10 +27,15 @@ 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) { int i; + int decoration_style = 0; rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; @@ -57,13 +62,26 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--decorate")) { - load_ref_decorations(); - rev->show_decorations = 1; + decoration_style = DECORATE_SHORT_REFS; + } else if (!prefixcmp(arg, "--decorate=")) { + const char *v = skip_prefix(arg, "--decorate="); + if (!strcmp(v, "full")) + decoration_style = DECORATE_FULL_REFS; + else if (!strcmp(v, "short")) + decoration_style = DECORATE_SHORT_REFS; + else + 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); } + if (decoration_style) { + rev->show_decorations = 1; + load_ref_decorations(decoration_style); + } } /* @@ -664,6 +682,10 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, log_write_email_headers(rev, head, &subject_start, &extra_headers, &need_8bit_cte); + for (i = 0; !need_8bit_cte && i < nr; i++) + if (has_non_ascii(list[i]->buffer)) + need_8bit_cte = 1; + msg = body; pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, encoding); diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 92637ac0ba..b0b5d8f6cb 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -765,7 +765,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 +774,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 +829,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); diff --git a/builtin-mailsplit.c b/builtin-mailsplit.c index ad5f6b593d..ee6ca0ebcd 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; @@ -88,20 +70,23 @@ static int split_one(FILE *mbox, const char *name, int allow_bare) * "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.c b/builtin-merge.c index 82b546689c..b6b84286b2 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -358,6 +358,7 @@ static void merge_name(const char *remote, struct strbuf *msg) struct strbuf buf = STRBUF_INIT; struct strbuf bname = STRBUF_INIT; const char *ptr; + char *found_ref; int len, early; strbuf_branchname(&bname, remote); @@ -368,14 +369,17 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!remote_head) die("'%s' does not point to a commit", remote); - strbuf_addstr(&buf, "refs/heads/"); - strbuf_addstr(&buf, remote); - resolve_ref(buf.buf, branch_head, 0, NULL); - - if (!hashcmp(remote_head->sha1, branch_head)) { - strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", - sha1_to_hex(branch_head), remote); - goto cleanup; + if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) { + if (!prefixcmp(found_ref, "refs/heads/")) { + strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", + sha1_to_hex(branch_head), remote); + goto cleanup; + } + if (!prefixcmp(found_ref, "refs/remotes/")) { + strbuf_addf(msg, "%s\t\tremote branch '%s' of .\n", + sha1_to_hex(branch_head), remote); + goto cleanup; + } } /* See if remote matches <name>^^^.. or <name>~<number> */ @@ -594,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 b592c367b2..1b20028c67 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 ef4bf6bc14..c4337480fd 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -86,7 +86,7 @@ static int pack_compression_level = Z_DEFAULT_COMPRESSION; static int pack_compression_seen; static unsigned long delta_cache_size = 0; -static unsigned long max_delta_cache_size = 0; +static unsigned long max_delta_cache_size = 256 * 1024 * 1024; static unsigned long cache_max_small_delta_size = 1000; static unsigned long window_memory_limit = 0; @@ -2098,6 +2098,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"; diff --git a/builtin-prune.c b/builtin-prune.c index 0ed9cce4a2..8459aec8e8 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 1d92e22f0a..67f6d96fbe 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -140,6 +140,7 @@ static int do_push(const char *repo, int flags) struct transport *transport = transport_get(remote, url[i]); int err; + int nonfastforward; if (receivepack) transport_set_option(transport, TRANS_OPT_RECEIVEPACK, receivepack); @@ -148,13 +149,19 @@ static int do_push(const char *repo, int flags) if (flags & TRANSPORT_PUSH_VERBOSE) fprintf(stderr, "Pushing to %s\n", url[i]); - err = transport_push(transport, refspec_nr, refspec, flags); + err = transport_push(transport, refspec_nr, refspec, flags, + &nonfastforward); err |= transport_disconnect(transport); if (!err) continue; error("failed to push some refs to '%s'", url[i]); + if (nonfastforward) { + printf("To prevent you from losing history, non-fast-forward updates were rejected.\n" + "Merge the remote changes before pushing again.\n" + "See 'non-fast forward' section of 'git push --help' for details.\n"); + } errs++; } return !!errs; @@ -168,6 +175,7 @@ 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), diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 9c2d634d6d..14c836b169 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -113,13 +113,15 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) argc = parse_options(argc, argv, unused_prefix, read_tree_options, read_tree_usage, 0); - if (read_cache_unmerged() && (opts.prefix || opts.merge)) - die("You need to resolve your current index first"); - prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); - stage = opts.merge = (opts.reset || opts.merge || prefix_set); + + 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; + } for (i = 0; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 6ec1d056e6..b771fe9b20 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-replace.c b/builtin-replace.c new file mode 100644 index 0000000000..fe3a647a36 --- /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 5fa1789d0c..0fc0b0743f 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"); @@ -261,7 +262,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 +303,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 47fb9f7baa..37e528e283 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-tag.c b/builtin-tag.c index a51a6d1ea2..c4790185eb 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 557148a693..685566e0b5 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -181,10 +181,10 @@ static void write_cached_object(struct object *obj) static int check_object(struct object *obj, int type, void *data) { if (!obj) - return 0; + return 1; if (obj->flags & FLAG_WRITTEN) - return 1; + return 0; if (type != OBJ_ANY && obj->type != type) die("object type mismatch"); @@ -195,22 +195,24 @@ static int check_object(struct object *obj, int type, void *data) if (type != obj->type || type <= 0) die("object of unexpected type"); obj->flags |= FLAG_WRITTEN; - return 1; + return 0; } if (fsck_object(obj, 1, fsck_error_function)) die("Error in object"); - if (!fsck_walk(obj, check_object, NULL)) + if (fsck_walk(obj, check_object, NULL)) die("Error on reachable objects of %s", sha1_to_hex(obj->sha1)); write_cached_object(obj); - return 1; + return 0; } static void write_rest(void) { unsigned i; - for (i = 0; i < nr_objects; i++) - check_object(obj_list[i].obj, OBJ_ANY, NULL); + for (i = 0; i < nr_objects; i++) { + if (obj_list[i].obj) + check_object(obj_list[i].obj, OBJ_ANY, NULL); + } } static void added_object(unsigned nr, enum object_type type, @@ -495,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 0000000000..2b3fddcc69 --- /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 ebd6dff940..b6079ae6cb 100644 --- a/builtin-verify-pack.c +++ b/builtin-verify-pack.c @@ -6,12 +6,19 @@ #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, chain_histogram[MAX_CHAIN+1]; + uint32_t nr_objects, i; + int cnt; + int stat_only = flags & VERIFY_PACK_STAT_ONLY; + unsigned long chain_histogram[MAX_CHAIN+1], baseobjects; nr_objects = p->num_objects; memset(chain_histogram, 0, sizeof(chain_histogram)); + baseobjects = 0; for (i = 0; i < nr_objects; i++) { const unsigned char *sha1; @@ -29,14 +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 (!delta_chain_length) - printf("%-6s %lu %lu %"PRIuMAX"\n", - type, size, store_size, (uintmax_t)offset); + if (!stat_only) + printf("%s ", sha1_to_hex(sha1)); + if (!delta_chain_length) { + if (!stat_only) + printf("%-6s %lu %lu %"PRIuMAX"\n", + type, size, store_size, (uintmax_t)offset); + baseobjects++; + } else { - 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 @@ -44,21 +56,29 @@ static void show_pack_info(struct packed_git *p) } } - for (i = 0; i <= MAX_CHAIN; i++) { - if (!chain_histogram[i]) + if (baseobjects) + printf("non delta: %lu object%s\n", + baseobjects, baseobjects > 1 ? "s" : ""); + + for (cnt = 1; cnt <= MAX_CHAIN; cnt++) { + if (!chain_histogram[cnt]) continue; - printf("chain length = %"PRIu32": %"PRIu32" object%s\n", i, - chain_histogram[i], chain_histogram[i] > 1 ? "s" : ""); + printf("chain length = %d: %lu object%s\n", cnt, + chain_histogram[cnt], + chain_histogram[cnt] > 1 ? "s" : ""); } if (chain_histogram[0]) - printf("chain length > %d: %"PRIu32" object%s\n", MAX_CHAIN, - chain_histogram[0], chain_histogram[0] > 1 ? "s" : ""); + printf("chain length > %d: %lu object%s\n", MAX_CHAIN, + chain_histogram[0], + chain_histogram[0] > 1 ? "s" : ""); } -static int verify_one_pack(const char *path, 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; @@ -94,14 +114,19 @@ 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); } } @@ -109,17 +134,20 @@ static int verify_one_pack(const char *path, int verbose) } static const char * const verify_pack_usage[] = { - "git verify-pack [-v|--verbose] <pack>...", + "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; + unsigned int flags = 0; int i; const struct option verify_pack_options[] = { - OPT__VERBOSE(&verbose), + OPT_BIT('v', "verbose", &flags, "verbose", + VERIFY_PACK_VERBOSE), + OPT_BIT('s', "stat-only", &flags, "show statistics only", + VERIFY_PACK_STAT_ONLY), OPT_END() }; @@ -129,7 +157,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) if (argc < 1) usage_with_options(verify_pack_usage, verify_pack_options); for (i = 0; i < argc; i++) { - if (verify_one_pack(argv[i], verbose)) + if (verify_one_pack(argv[i], flags)) err = 1; discard_revindex(); } @@ -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 @@ -330,7 +330,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)) @@ -468,13 +468,16 @@ extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_obje extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object); extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st); +/* "careful lstat()" */ +extern int check_path(const char *path, int len, struct stat *st, int skiplen); + #define REFRESH_REALLY 0x0001 /* ignore_valid */ #define REFRESH_UNMERGED 0x0002 /* allow unmerged */ #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; @@ -509,6 +512,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; @@ -516,6 +520,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; @@ -652,7 +657,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 *); @@ -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; } @@ -64,6 +64,7 @@ enum cmit_fmt { }; extern int non_ascii(int); +extern int has_non_ascii(const char *text); struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ extern char *reencode_commit_message(const struct commit *commit, const char **encoding_p); @@ -122,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); diff --git a/compat/bswap.h b/compat/bswap.h new file mode 100644 index 0000000000..7246a12c6e --- /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.h b/compat/mingw.h index c1859c5480..948de66eb5 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 diff --git a/compat/snprintf.c b/compat/snprintf.c index 6c0fb056a5..4d07087abd 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 diff --git a/configure.ac b/configure.ac index 3f1922d0bf..b09b8e446f 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. diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 745b5fb78b..bf688e12e6 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -674,6 +674,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 +696,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 +1049,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 " @@ -1536,6 +1539,7 @@ _git_config () __gitcomp " add.ignore-errors alias. + apply.ignorewhitespace apply.whitespace branch.autosetupmerge branch.autosetuprebase diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index eace9c18eb..8c70ad8b7f 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 () diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index 342529db30..38438f3c4a 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -201,7 +201,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 +224,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() @@ -950,10 +953,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 +1044,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 +1085,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 +1112,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"]) @@ -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); } @@ -135,7 +135,7 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode) } /* Give years and months for 5 years or so */ if (diff < 1825) { - unsigned long years = (diff + 183) / 365; + unsigned long years = diff / 365; unsigned long months = (diff % 365 + 15) / 30; int n; n = snprintf(timebuf, sizeof(timebuf), "%lu year%s", diff --git a/diff-lib.c b/diff-lib.c index ad2a4cde74..0c74ef5cbe 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); @@ -861,12 +861,20 @@ int is_empty_dir(const char *path) return ret; } -int remove_dir_recursively(struct strbuf *path, int only_empty) +int remove_dir_recursively(struct strbuf *path, int flag) { - DIR *dir = opendir(path->buf); + DIR *dir; struct dirent *e; int ret = 0, original_len = path->len, len; + int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY); + unsigned char submodule_head[20]; + if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) && + !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) + /* Do not descend and nuke a nested git work tree. */ + return 0; + + dir = opendir(path->buf); if (!dir) return -1; if (path->buf[original_len - 1] != '/') @@ -88,7 +88,10 @@ static inline int is_dot_or_dotdot(const char *name) extern int is_empty_dir(const char *dir); extern void setup_standard_excludes(struct dir_struct *dir); -extern int remove_dir_recursively(struct strbuf *path, int only_empty); + +#define REMOVE_DIR_EMPTY_ONLY 01 +#define REMOVE_DIR_KEEP_NESTED_GIT 02 +extern int remove_dir_recursively(struct strbuf *path, int flag); /* tries to remove the path with empty directories along it, ignores ENOENT */ extern int remove_path(const char *path); @@ -175,6 +175,23 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout return 0; } +/* + * This is like 'lstat()', except it refuses to follow symlinks + * in the path, after skipping "skiplen". + */ +int check_path(const char *path, int len, struct stat *st, int skiplen) +{ + const char *slash = path + len; + + while (path < slash && *slash != '/') + slash--; + if (!has_dirs_only_path(path, slash - path, skiplen)) { + errno = ENOENT; + return -1; + } + return lstat(path, st); +} + int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath) { static char path[PATH_MAX + 1]; @@ -188,7 +205,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t strcpy(path + len, ce->name); len += ce_namelen(ce); - if (!lstat(path, &st)) { + if (!check_path(path, len, &st, state->base_dir_len)) { unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); if (!changed) return 0; diff --git a/environment.c b/environment.c index 8f5eaa7dd8..5de6837840 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 df9f231635..06f70602cc 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -841,6 +841,10 @@ sub coalesce_overlapping_hunks { my ($last_o_ctx, $last_was_dirty); for (grep { $_->{USE} } @in) { + if ($_->{TYPE} ne 'hunk') { + push @out, $_; + next; + } my $text = $_->{TEXT}; my ($o_ofs) = parse_hunk_header($text->[0]); if (defined $last_o_ctx && @@ -16,6 +16,8 @@ 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 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 @@ -191,13 +193,33 @@ check_patch_format () { esac ;; esac + if test -z "$patch_format" && + test -n "$l1" && + test -n "$l2" && + test -n "$l3" + then + # This begins with three non-empty lines. Is this a + # piece of e-mail a-la RFC2822? Grab all the headers, + # discarding the indented remainder of folded lines, + # and see if it looks like that they all begin with the + # header field names... + sed -n -e '/^$/q' -e '/^[ ]/d' -e p "$1" | + egrep -v '^[A-Za-z]+(-[A-Za-z]+)*:' >/dev/null || + patch_format=mbox + fi } < "$1" || clean_abort } 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) @@ -254,7 +276,11 @@ split_patches () { msgnum= ;; *) - clean_abort "Patch format $patch_format is not supported." + if test -n "$parse_patch" ; then + clean_abort "Patch format $patch_format is not supported." + else + clean_abort "Patch format detection failed." + fi ;; esac } @@ -303,7 +329,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 ;; diff --git a/git-bisect.sh b/git-bisect.sh index 8969553658..6f6f03966f 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -405,7 +405,7 @@ bisect_run () { exit $res fi - if grep "is first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then + if grep "is the first bad commit" "$GIT_DIR/BISECT_RUN" > /dev/null; then echo "bisect run success" exit 0; fi diff --git a/git-compat-util.h b/git-compat-util.h index 9f941e42b1..e5e9f39c56 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 e439202961..593832d813 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -252,7 +252,8 @@ sub conn { } }; } - $pass="A" unless $pass; + + $pass = $self->_scramble($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-filter-branch.sh b/git-filter-branch.sh index 37e044db40..a480d6fc70 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -97,12 +97,12 @@ set_ident () { echo "case \"\$GIT_${uid}_NAME\" in \"\") GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\" && export GIT_${uid}_NAME;; esac" } -USAGE="[--env-filter <command>] [--tree-filter <command>] \ -[--index-filter <command>] [--parent-filter <command>] \ -[--msg-filter <command>] [--commit-filter <command>] \ -[--tag-name-filter <command>] [--subdirectory-filter <directory>] \ -[--original <namespace>] [-d <directory>] [-f | --force] \ -[<rev-list options>...]" +USAGE="[--env-filter <command>] [--tree-filter <command>] + [--index-filter <command>] [--parent-filter <command>] + [--msg-filter <command>] [--commit-filter <command>] + [--tag-name-filter <command>] [--subdirectory-filter <directory>] + [--original <namespace>] [-d <directory>] [-f | --force] + [<rev-list options>...]" OPTIONS_SPEC= . git-sh-setup diff --git a/git-instaweb.sh b/git-instaweb.sh index 32f6496b0d..d96eddbe56 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 () { @@ -278,7 +297,7 @@ EOF # check to see if Dennis Stosberg's mod_perl compatibility patch # (<20060621130708.Gcbc6e5c@leonov.stosberg.net>) has been applied - if test -f "$module_path/mod_perl.so" && grep '^our $gitbin' \ + if test -f "$module_path/mod_perl.so" && grep 'MOD_PERL' \ "$GIT_DIR/gitweb/gitweb.cgi" >/dev/null then # favor mod_perl if available @@ -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 0f24182974..0bbd5bf7df 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 18bc6946cf..2315d95a9f 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 --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-stash.sh b/git-stash.sh index 03e589f764..d61c9d03bc 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -162,10 +162,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 +180,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" diff --git a/git-submodule.sh b/git-submodule.sh index ebed711da4..bfbd36b6f4 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 d075810724..ce4fef9d34 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: @@ -3262,7 +3288,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; @@ -3317,12 +3343,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", @@ -5495,7 +5533,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}}-> @@ -5564,7 +5602,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; @@ -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/gitk-git/gitk b/gitk-git/gitk index 4604c831fe..8c08310e6d 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/git-favicon.png b/gitweb/git-favicon.png Binary files differindex de637c0608..aae35a70e7 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 16ae8d5382..f4ede2e944 100644 --- a/gitweb/git-logo.png +++ b/gitweb/git-logo.png diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index d05bc37646..8f68fe3091 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 b453ed0452..24b219310a 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -415,7 +415,7 @@ sub gitweb_get_feature { @{$feature{$name}{'default'}}); if (!$override) { return @defaults; } if (!defined $sub) { - warn "feature $name is not overrideable"; + warn "feature $name is not overridable"; return @defaults; } return $sub->(@defaults); @@ -1524,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; } } @@ -2584,7 +2584,7 @@ sub parse_commit_text { } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) { push @parents, $1; } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) { - $co{'author'} = $1; + $co{'author'} = to_utf8($1); $co{'author_epoch'} = $2; $co{'author_tz'} = $3; if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) { @@ -2594,10 +2594,9 @@ sub parse_commit_text { $co{'author_name'} = $co{'author'}; } } elsif ($line =~ m/^committer (.*) ([0-9]+) (.*)$/) { - $co{'committer'} = $1; + $co{'committer'} = to_utf8($1); $co{'committer_epoch'} = $2; $co{'committer_tz'} = $3; - $co{'committer_name'} = $co{'committer'}; if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) { $co{'committer_name'} = $1; $co{'committer_email'} = $2; @@ -4815,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 = (); @@ -4833,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); @@ -4852,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\""; @@ -4862,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" }, @@ -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); @@ -79,6 +79,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; @@ -302,7 +302,7 @@ const char *help_unknown_cmd(const char *cmd) struct cmdnames main_cmds, other_cmds; memset(&main_cmds, 0, sizeof(main_cmds)); - memset(&other_cmds, 0, sizeof(main_cmds)); + memset(&other_cmds, 0, sizeof(other_cmds)); memset(&aliases, 0, sizeof(aliases)); git_config(git_unknown_cmd_config, NULL); @@ -334,7 +334,7 @@ const char *help_unknown_cmd(const char *cmd) const char *assumed = main_cmds.names[0]->name; main_cmds.names[0] = NULL; clean_cmdnames(&main_cmds); - fprintf(stderr, "WARNING: You called a Git program named '%s', " + fprintf(stderr, "WARNING: You called a Git command named '%s', " "which does not exist.\n" "Continuing under the assumption that you meant '%s'\n", cmd, assumed); @@ -719,7 +719,9 @@ void append_remote_object_url(struct strbuf *buf, const char *url, const char *hex, int only_two_digit_prefix) { - strbuf_addf(buf, "%s/objects/%.*s/", url, 2, hex); + end_url_with_slash(buf, url); + + strbuf_addf(buf, "objects/%.*s/", 2, hex); if (!only_two_digit_prefix) strbuf_addf(buf, "%s", hex+2); } @@ -1004,7 +1006,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]; @@ -1018,8 +1019,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); @@ -1035,7 +1035,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); @@ -1059,6 +1059,8 @@ struct http_pack_request *new_http_pack_request( abort: free(filename); + free(preq->url); + free(preq); return NULL; } @@ -1098,7 +1100,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; @@ -1152,8 +1153,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 @@ -1189,7 +1189,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; + } } } @@ -1198,7 +1202,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); /* @@ -1218,9 +1222,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; } @@ -1285,5 +1289,10 @@ void release_http_object_request(struct http_object_request *freq) free(freq->url); freq->url = NULL; } - freq->slot = NULL; + if (freq->slot != NULL) { + freq->slot->callback_func = NULL; + freq->slot->callback_data = NULL; + release_active_slot(freq->slot); + freq->slot = NULL; + } } diff --git a/ll-merge.c b/ll-merge.c index 0571564ddf..2d6b6d6cb1 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/log-tree.c b/log-tree.c index 6f73c17d74..1c9eefee33 100644 --- a/log-tree.c +++ b/log-tree.c @@ -25,7 +25,8 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in struct object *obj = parse_object(sha1); if (!obj) return 0; - refname = prettify_refname(refname); + if (!cb_data || *(int *)cb_data == DECORATE_SHORT_REFS) + refname = prettify_refname(refname); add_name_decoration("", refname, obj); while (obj->type == OBJ_TAG) { obj = ((struct tag *)obj)->tagged; @@ -36,12 +37,12 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in return 0; } -void load_ref_decorations(void) +void load_ref_decorations(int flags) { static int loaded; if (!loaded) { loaded = 1; - for_each_ref(add_ref_decoration, NULL); + for_each_ref(add_ref_decoration, &flags); } } @@ -168,18 +169,6 @@ static unsigned int digits_in_number(unsigned int number) return result; } -static int has_non_ascii(const char *s) -{ - int ch; - if (!s) - return 0; - while ((ch = *s++) != '\0') { - if (non_ascii(ch)) - return 1; - } - return 0; -} - void get_patch_filename(struct commit *commit, int nr, const char *suffix, struct strbuf *buf) { diff --git a/log-tree.h b/log-tree.h index 20b5caf1aa..3f7b40027b 100644 --- a/log-tree.h +++ b/log-tree.h @@ -17,7 +17,7 @@ void log_write_email_headers(struct rev_info *opt, struct commit *commit, const char **subject_p, const char **extra_headers_p, int *need_8bit_cte_p); -void load_ref_decorations(void); +void load_ref_decorations(int flags); #define FORMAT_PATCH_NAME_MAX 64 void get_patch_filename(struct commit *commit, int nr, const char *suffix, diff --git a/merge-recursive.c b/merge-recursive.c index d415c4188d..10d7913b06 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -952,9 +952,31 @@ static int process_renames(struct merge_options *o, "%s added in %s", ren1_src, ren1_dst, branch1, ren1_dst, branch2); - new_path = unique_path(o, ren1_dst, branch2); - output(o, 1, "Adding as %s instead", new_path); - update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); + if (o->call_depth) { + struct merge_file_info mfi; + struct diff_filespec one, a, b; + + one.path = a.path = b.path = + (char *)ren1_dst; + hashcpy(one.sha1, null_sha1); + one.mode = 0; + hashcpy(a.sha1, ren1->pair->two->sha1); + a.mode = ren1->pair->two->mode; + hashcpy(b.sha1, dst_other.sha1); + b.mode = dst_other.mode; + mfi = merge_file(o, &one, &a, &b, + branch1, + branch2); + output(o, 1, "Adding merged %s", ren1_dst); + update_file(o, 0, + mfi.sha, + mfi.mode, + ren1_dst); + } else { + new_path = unique_path(o, ren1_dst, branch2); + output(o, 1, "Adding as %s instead", new_path); + update_file(o, 0, dst_other.sha1, dst_other.mode, new_path); + } } else if ((item = string_list_lookup(ren1_dst, renames2Dst))) { ren2 = item->util; clean_merge = 0; @@ -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 95a4ebf496..0000000000 --- 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 aa48a46cf7..0000000000 --- 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/parse-options.c b/parse-options.c index 3b71fbb541..a64a4d6ee2 100644 --- a/parse-options.c +++ b/parse-options.c @@ -549,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 b32587ad7c..f295a2cf85 100644 --- a/parse-options.h +++ b/parse-options.h @@ -145,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 { @@ -86,6 +86,18 @@ int non_ascii(int ch) return !isascii(ch) || ch == '\033'; } +int has_non_ascii(const char *s) +{ + int ch; + if (!s) + return 0; + while ((ch = *s++) != '\0') { + if (non_ascii(ch)) + return 1; + } + return 0; +} + static int is_rfc2047_special(char ch) { return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_')); @@ -571,7 +583,7 @@ static void format_decoration(struct strbuf *sb, const struct commit *commit) struct name_decoration *d; const char *prefix = " ("; - load_ref_decorations(); + load_ref_decorations(DECORATE_SHORT_REFS); d = lookup_decoration(&name_decoration, &commit->object); while (d) { strbuf_addstr(sb, prefix); diff --git a/read-cache.c b/read-cache.c index 4e3e272ee4..1bbaf1cffb 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, @@ -821,7 +826,7 @@ static int remove_empty_directories(const char *file) strbuf_init(&path, 20); strbuf_addstr(&path, file); - result = remove_dir_recursively(&path, 1); + result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY); strbuf_release(&path); @@ -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/replace_object.c b/replace_object.c new file mode 100644 index 0000000000..eb59604fd3 --- /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; +} diff --git a/revision.c b/revision.c index 9f5dac5f1d..35eca4a361 100644 --- a/revision.c +++ b/revision.c @@ -1052,7 +1052,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->simplify_by_decoration = 1; revs->limited = 1; revs->prune = 1; - load_ref_decorations(); + load_ref_decorations(DECORATE_SHORT_REFS); } else if (!strcmp(arg, "--date-order")) { revs->lifo = 0; revs->topo_order = 1; @@ -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 fb74492714..9d0dddbcbc 100644 --- a/revision.h +++ b/revision.h @@ -15,6 +15,9 @@ #define SYMMETRIC_LEFT (1u<<8) #define ALL_REV_FLAGS ((1u<<9)-1) +#define DECORATE_SHORT_REFS 1 +#define DECORATE_FULL_REFS 2 + struct rev_info; struct log_info; @@ -165,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 ff3d8e2d8b..f3e7abb7de 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; @@ -173,6 +184,9 @@ int start_command(struct child_process *cmd) } 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 +203,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 +213,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 +233,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 +297,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 +362,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 +406,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 e345502843..0c00b25ff2 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 1d7b1b3b4f..8b3cf028ed 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 1d996a1990..4ea0b18d0a 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/symlinks.c b/symlinks.c index 4bdded39c5..7b0a86d357 100644 --- a/symlinks.c +++ b/symlinks.c @@ -91,6 +91,10 @@ static int lstat_cache(struct cache_def *cache, const char *name, int len, longest_path_match(name, len, cache->path, cache->len, &previous_slash); match_flags = cache->flags & track_flags & (FL_NOENT|FL_SYMLINK); + + if (!(track_flags & FL_FULLPATH) && match_len == len) + match_len = last_slash = previous_slash; + if (match_flags && match_len == cache->len) return match_flags; /* diff --git a/t/Makefile b/t/Makefile index bf816fc850..bd09390d32 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/lib-git-svn.sh b/t/lib-git-svn.sh index 5654962343..fd8631f906 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/t0001-init.sh b/t/t0001-init.sh index 49caa29061..5386504790 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -244,6 +244,12 @@ test_expect_success 'init recreates a new bare directory' ' 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" @@ -251,7 +257,7 @@ test_expect_success 'init creates a new deep directory' ' 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" ls.out && + ! grep -v "^drwxrw[sx]r-x" lsab.out && ls -ld newdir/a/b/c > lsc.out && ! grep -v "^drwxrw[sx]---" lsc.out ) diff --git a/t/t1009-read-tree-new-index.sh b/t/t1009-read-tree-new-index.sh new file mode 100755 index 0000000000..59b3aa4bc4 --- /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/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh index f7e1a735ec..de3edb5d57 100755 --- a/t/t2000-checkout-cache-clash.sh +++ b/t/t2000-checkout-cache-clash.sh @@ -48,4 +48,13 @@ test_expect_success \ 'git checkout-index conflicting paths.' \ 'test -f path0 && test -d path1 && test -f path1/file1' +test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' ' + mkdir -p tar/get && + ln -s tar/get there && + echo first && + git checkout-index -a -f --prefix=there/ && + echo second && + git checkout-index -a -f --prefix=there/ +' + test_done diff --git a/t/t2015-checkout-unborn.sh b/t/t2015-checkout-unborn.sh new file mode 100755 index 0000000000..c551d39a66 --- /dev/null +++ b/t/t2015-checkout-unborn.sh @@ -0,0 +1,40 @@ +#!/bin/sh + +test_description='checkout from unborn branch protects contents' +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir parent && + (cd parent && + git init && + echo content >file && + git add file && + git commit -m base + ) && + git fetch parent master:origin +' + +test_expect_success 'checkout from unborn preserves untracked files' ' + echo precious >expect && + echo precious >file && + test_must_fail git checkout -b new origin && + test_cmp expect file +' + +test_expect_success 'checkout from unborn preserves index contents' ' + echo precious >expect && + echo precious >file && + git add file && + test_must_fail git checkout -b new origin && + test_cmp expect file && + git show :file >file && + test_cmp expect file +' + +test_expect_success 'checkout from unborn merges identical index contents' ' + echo content >file && + git add file && + git checkout -b new origin +' + +test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index c5c29ccc4f..4e6a44b623 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 a973628e8e..4cae019521 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/t3409-rebase-preserve-merges.sh b/t/t3409-rebase-preserve-merges.sh index e6c832780f..297d165476 100755 --- a/t/t3409-rebase-preserve-merges.sh +++ b/t/t3409-rebase-preserve-merges.sh @@ -71,7 +71,7 @@ test_expect_success 'rebase -p fakes interactive rebase' ' git fetch && git rebase -p origin/topic && test 1 = $(git rev-list --all --pretty=oneline | grep "Modify A" | wc -l) && - test 1 = $(git rev-list --all --pretty=oneline | grep "Merge commit" | wc -l) + test 1 = $(git rev-list --all --pretty=oneline | grep "Merge remote branch " | wc -l) ) ' diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh index 6533505218..14a23cd872 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 80019ee072..ee0a6cccfd 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/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index fd2a55a5c2..62fd65e18d 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -163,6 +163,17 @@ test_expect_success FILEMODE 'stage mode but not hunk' ' git diff file | grep "+content" ' + +test_expect_success FILEMODE 'stage mode and hunk' ' + git reset --hard && + echo content >>file && + chmod +x file && + printf "y\\ny\\n" | git add -p && + git diff --cached file | grep "new mode" && + git diff --cached file | grep "+content" && + test -z "$(git diff file)" +' + # end of tests disabled when filemode is not usable test_expect_success 'setup again' ' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 8b33321f8c..8e3694ed5b 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -207,6 +207,7 @@ log --root --cc --patch-with-stat --summary master log -SF master log -SF -p master log --decorate --all +log --decorate=full --all rev-list --parents HEAD rev-list --children HEAD diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all new file mode 100644 index 0000000000..903d9d9659 --- /dev/null +++ b/t/t4013/diff.log_--decorate=full_--all @@ -0,0 +1,34 @@ +$ git log --decorate=full --all +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 922a8941ed..531f5b795c 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 4ea42e00da..a7602cf923 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 0000000000..9d9498bd95 --- /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 0000000000..484654d6e4 --- /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/t4150-am.sh b/t/t4150-am.sh index a12bf84623..8296605234 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -77,6 +77,12 @@ test_expect_success setup ' git commit -s -F msg && git tag second && git format-patch --stdout first >patch1 && + { + echo "X-Fake-Field: Line One" && + echo "X-Fake-Field: Line Two" && + echo "X-Fake-Field: Line Three" && + git format-patch --stdout first | sed -e "1d" + } > patch1.eml && sed -n -e "3,\$p" msg >file && git add file && test_tick && @@ -108,6 +114,15 @@ test_expect_success 'am applies patch correctly' ' test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" ' +test_expect_success 'am applies patch e-mail not in a mbox' ' + git checkout first && + git am patch1.eml && + ! test -d .git/rebase-apply && + test -z "$(git diff second)" && + test "$(git rev-parse second)" = "$(git rev-parse HEAD)" && + test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)" +' + GIT_AUTHOR_NAME="Another Thor" GIT_AUTHOR_EMAIL="a.thor@example.com" GIT_COMMITTER_NAME="Co M Miter" diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 48e0088b47..1e952ca55b 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -320,11 +320,11 @@ test_expect_success 'set up more tangled history' ' ' cat > expect <<\EOF -* Merge branch 'reach' +* Merge commit 'reach' |\ | \ | \ -*-. \ Merge branches 'octopus-a' and 'octopus-b' +*-. \ Merge commit 'octopus-a'; commit 'octopus-b' |\ \ \ * | | | seventh | | * | octopus-b diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index a8c2ca2a78..18376d6608 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/t5510-fetch.sh b/t/t5510-fetch.sh index bee3424fed..d13c806624 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -9,6 +9,11 @@ test_description='Per branch config variables affects "git fetch". D=`pwd` +test_bundle_object_count () { + git verify-pack -v "$1" >verify.out && + test "$2" = $(grep '^[0-9a-f]\{40\} ' verify.out | wc -l) +} + test_expect_success setup ' echo >file original && git add file && @@ -146,6 +151,7 @@ test_expect_success 'unbundle 1' ' test_must_fail git fetch "$D/bundle1" master:master ' + test_expect_success 'bundle 1 has only 3 files ' ' cd "$D" && ( @@ -156,8 +162,7 @@ test_expect_success 'bundle 1 has only 3 files ' ' cat ) <bundle1 >bundle.pack && git index-pack bundle.pack && - verify=$(git verify-pack -v bundle.pack) && - test 4 = $(echo "$verify" | wc -l) + test_bundle_object_count bundle.pack 3 ' test_expect_success 'unbundle 2' ' @@ -180,7 +185,7 @@ test_expect_success 'bundle does not prerequisite objects' ' cat ) <bundle3 >bundle.pack && git index-pack bundle.pack && - test 4 = $(git verify-pack -v bundle.pack | wc -l) + test_bundle_object_count bundle.pack 3 ' test_expect_success 'bundle should be able to create a full history' ' diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index e78d40242a..dd2ee842e0 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 757cc19ecc..a696b8791b 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -54,7 +54,10 @@ 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 && - grep "waitpid (async) failed" 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' ' diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh new file mode 100644 index 0000000000..65d8d474bc --- /dev/null +++ b/t/t5531-deep-submodule-push.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='unpack-objects' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir pub.git && + GIT_DIR=pub.git git init --bare + GIT_DIR=pub.git git config receive.fsckobjects true && + mkdir work && + ( + cd work && + git init && + mkdir -p gar/bage && + ( + cd gar/bage && + git init && + >junk && + git add junk && + git commit -m "Initial junk" + ) && + git add gar/bage && + git commit -m "Initial superproject" + ) +' + +test_expect_success push ' + ( + cd work && + git push ../pub.git master + ) +' + +test_done 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 0000000000..8b146fb432 --- /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 0000000000..27fd52b7be --- /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/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 1315bab595..def397c53a 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -175,7 +175,7 @@ test_expect_success 'bisect skip: successfull result' ' git bisect start $HASH4 $HASH1 && git bisect skip && git bisect bad > my_bisect_log.txt && - grep "$HASH2 is first bad commit" my_bisect_log.txt && + grep "$HASH2 is the first bad commit" my_bisect_log.txt && git bisect reset ' @@ -261,7 +261,7 @@ test_expect_success \ git bisect good $HASH1 && git bisect bad $HASH4 && git bisect run ./test_script.sh > my_bisect_log.txt && - grep "$HASH3 is first bad commit" my_bisect_log.txt && + grep "$HASH3 is the first bad commit" my_bisect_log.txt && git bisect reset' # We want to automatically find the commit that @@ -274,7 +274,7 @@ test_expect_success \ chmod +x test_script.sh && git bisect start $HASH4 $HASH1 && git bisect run ./test_script.sh > my_bisect_log.txt && - grep "$HASH4 is first bad commit" my_bisect_log.txt && + grep "$HASH4 is the first bad commit" my_bisect_log.txt && git bisect reset' # $HASH1 is good, $HASH5 is bad, we skip $HASH3 @@ -287,14 +287,14 @@ test_expect_success 'bisect skip: add line and then a new test' ' git bisect start $HASH5 $HASH1 && git bisect skip && git bisect good > my_bisect_log.txt && - grep "$HASH5 is first bad commit" my_bisect_log.txt && + grep "$HASH5 is the first bad commit" my_bisect_log.txt && git bisect log > log_to_replay.txt && git bisect reset ' test_expect_success 'bisect skip and bisect replay' ' git bisect replay log_to_replay.txt > my_bisect_log.txt && - grep "$HASH5 is first bad commit" my_bisect_log.txt && + grep "$HASH5 is the first bad commit" my_bisect_log.txt && git bisect reset ' @@ -335,7 +335,7 @@ test_expect_success 'bisect run & skip: find first bad' ' chmod +x test_script.sh && git bisect start $HASH7 $HASH1 && git bisect run ./test_script.sh > my_bisect_log.txt && - grep "$HASH6 is first bad commit" my_bisect_log.txt + grep "$HASH6 is the first bad commit" my_bisect_log.txt ' test_expect_success 'bisect skip only one range' ' @@ -385,7 +385,7 @@ test_expect_success 'bisect does not create a "bisect" branch' ' rev_hash6=$(git rev-parse --verify HEAD) && test "$rev_hash6" = "$HASH6" && git bisect good > my_bisect_log.txt && - grep "$HASH7 is first bad commit" my_bisect_log.txt && + grep "$HASH7 is the first bad commit" my_bisect_log.txt && git bisect reset && rev_hash6=$(git rev-parse --verify bisect) && test "$rev_hash6" = "$HASH6" && @@ -534,7 +534,7 @@ test_expect_success 'restricting bisection on one dir' ' para1=$(git rev-parse --verify HEAD) && test "$para1" = "$PARA_HASH1" && git bisect bad > my_bisect_log.txt && - grep "$PARA_HASH1 is first bad commit" my_bisect_log.txt + grep "$PARA_HASH1 is the first bad commit" my_bisect_log.txt ' test_expect_success 'restricting bisection on one dir and a file' ' @@ -552,7 +552,7 @@ test_expect_success 'restricting bisection on one dir and a file' ' para1=$(git rev-parse --verify HEAD) && test "$para1" = "$PARA_HASH1" && git bisect good > my_bisect_log.txt && - grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt + grep "$PARA_HASH4 is the first bad commit" my_bisect_log.txt ' test_expect_success 'skipping away from skipped commit' ' diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh new file mode 100755 index 0000000000..5b96fb0b37 --- /dev/null +++ b/t/t6035-merge-dir-to-symlink.sh @@ -0,0 +1,93 @@ +#!/bin/sh + +test_description='merging when a directory was replaced with a symlink' +. ./test-lib.sh + +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + +test_expect_success 'create a commit where dir a/b changed to symlink' ' + mkdir -p a/b/c a/b-2/c && + > a/b/c/d && + > a/b-2/c/d && + > a/x && + git add -A && + git commit -m base && + git tag start && + rm -rf a/b && + ln -s b-2 a/b && + git add -A && + git commit -m "dir to symlink" +' + +test_expect_success 'keep a/b-2/c/d across checkout' ' + git checkout HEAD^0 && + git reset --hard master && + git rm --cached a/b && + git commit -m "untracked symlink remains" && + git checkout start^0 && + test -f a/b-2/c/d +' + +test_expect_success 'checkout should not have deleted a/b-2/c/d' ' + git checkout HEAD^0 && + git reset --hard master && + git checkout start^0 && + test -f a/b-2/c/d +' + +test_expect_success 'setup for merge test' ' + git reset --hard && + test -f a/b-2/c/d && + echo x > a/x && + git add a/x && + git commit -m x && + git tag baseline +' + +test_expect_success 'do not lose a/b-2/c/d in merge (resolve)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s resolve master && + test -h a/b && + test -f a/b-2/c/d +' + +test_expect_failure 'do not lose a/b-2/c/d in merge (recursive)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s recursive master && + test -h a/b && + test -f a/b-2/c/d +' + +test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' + git reset --hard && + git checkout start^0 && + rm -rf a/b-2 && + ln -s b a/b-2 && + git add -A && + git commit -m "dir a/b-2 to symlink" && + git tag test2 +' + +test_expect_failure 'merge should not have conflicts (resolve)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s resolve test2 && + test -h a/b-2 && + test -f a/b/c/d +' + +test_expect_failure 'merge should not have conflicts (recursive)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s recursive test2 && + test -h a/b-2 && + test -f a/b/c/d +' + +test_done diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh new file mode 100755 index 0000000000..b874141658 --- /dev/null +++ b/t/t6036-recursive-corner-cases.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +test_description='recursive merge corner cases' + +. ./test-lib.sh + +# +# L1 L2 +# o---o +# / \ / \ +# o X ? +# \ / \ / +# o---o +# R1 R2 +# + +test_expect_success setup ' + ten="0 1 2 3 4 5 6 7 8 9" + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 +' + +test_expect_success merge ' + git reset --hard && + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 +' + +test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh new file mode 100755 index 0000000000..8b8bd81c09 --- /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 b13aa7e89a..b4709e28b5 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 0000000000..1044aa6549 --- /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 e637c7d4db..e85ff02c3e 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/t7300-clean.sh b/t/t7300-clean.sh index 929d5d4d3b..118c6ebb18 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -380,4 +380,43 @@ test_expect_success 'removal failure' ' ' chmod 755 foo +test_expect_success 'nested git work tree' ' + rm -fr foo bar && + mkdir foo bar && + ( + cd foo && + git init && + >hello.world + git add . && + git commit -a -m nested + ) && + ( + cd bar && + >goodbye.people + ) && + git clean -f -d && + test -f foo/.git/index && + test -f foo/hello.world && + ! test -d bar +' + +test_expect_success 'force removal of nested git work tree' ' + rm -fr foo bar && + mkdir foo bar && + ( + cd foo && + git init && + >hello.world + git add . && + git commit -a -m nested + ) && + ( + cd bar && + >goodbye.people + ) && + git clean -f -f -d && + ! test -d foo && + ! test -d bar +' + test_done diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 61498293b9..6cc16c39fe 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 0000000000..2a527750ce --- /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 cc16d3f05d..cc16d3f05d 100755 --- a/t/t7406-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh diff --git a/t/t7608-merge-messages.sh b/t/t7608-merge-messages.sh new file mode 100755 index 0000000000..28d56797b1 --- /dev/null +++ b/t/t7608-merge-messages.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +test_description='test auto-generated merge messages' +. ./test-lib.sh + +check_oneline() { + echo "$1" | sed "s/Q/'/g" >expect && + git log -1 --pretty=tformat:%s >actual && + test_cmp expect actual +} + +test_expect_success 'merge local branch' ' + test_commit master-1 && + git checkout -b local-branch && + test_commit branch-1 && + git checkout master && + test_commit master-2 && + git merge local-branch && + check_oneline "Merge branch Qlocal-branchQ" +' + +test_expect_success 'merge octopus branches' ' + git checkout -b octopus-a master && + test_commit octopus-1 && + git checkout -b octopus-b master && + test_commit octopus-2 && + git checkout master && + git merge octopus-a octopus-b && + check_oneline "Merge branches Qoctopus-aQ and Qoctopus-bQ" +' + +test_expect_success 'merge tag' ' + git checkout -b tag-branch master && + test_commit tag-1 && + git checkout master && + test_commit master-3 && + git merge tag-1 && + check_oneline "Merge commit Qtag-1Q" +' + +test_expect_success 'ambiguous tag' ' + git checkout -b ambiguous master && + test_commit ambiguous && + git checkout master && + test_commit master-4 && + git merge ambiguous && + check_oneline "Merge commit QambiguousQ" +' + +test_expect_success 'remote branch' ' + git checkout -b remote master && + test_commit remote-1 && + git update-ref refs/remotes/origin/master remote && + git checkout master && + test_commit master-5 && + git merge origin/master && + check_oneline "Merge remote branch Qorigin/masterQ" +' + +test_done diff --git a/t/t8005/iso8859-5.txt b/t/t8005/iso8859-5.txt deleted file mode 100644 index 2e4b80c8df..0000000000 --- a/t/t8005/iso8859-5.txt +++ /dev/null @@ -1,2 +0,0 @@ -ISO8859_5_NAME="¸ÒÐÝ ¿ÕâàÞÒØç ÁØÔÞàÞÒ" -ISO8859_5_MSG="ÂÕáâÞÒÞÕ áÞÞÑéÕÝØÕ" diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 9da4178c94..929499e996 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 78610b61e6..bbfd7f4793 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 3a9e07768d..901b8e09fb 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 f159ab689b..9d9ebd533c 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 03705fa4ce..5280e5f1e4 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 f2ba2d1da3..99f69c6a0b 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 0000000000..7600a35cd4 --- /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 0000000000..16852d26ae --- /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/test-lib.sh b/t/test-lib.sh index 5fdc5d94a2..f2ca536472 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/transport.c b/transport.c index de0d5874a3..f7e1663d18 100644 --- a/transport.c +++ b/transport.c @@ -396,7 +396,6 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons { const char **argv; int argc; - int err; if (flags & TRANSPORT_PUSH_MIRROR) return error("http transport does not support mirror mode"); @@ -416,20 +415,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; + return !!run_command_v_opt(argv, RUN_GIT_CMD); } static struct ref *get_refs_via_curl(struct transport *transport, int for_push) @@ -681,6 +667,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) { @@ -820,7 +821,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i } static void print_push_status(const char *dest, struct ref *refs, - int verbose, int porcelain) + int verbose, int porcelain, int * nonfastforward) { struct ref *ref; int n = 0; @@ -835,11 +836,14 @@ static void print_push_status(const char *dest, struct ref *refs, if (ref->status == REF_STATUS_OK) n += print_one_push_status(ref, dest, n, porcelain); + *nonfastforward = 0; for (ref = refs; ref; ref = ref->next) { if (ref->status != REF_STATUS_NONE && ref->status != REF_STATUS_UPTODATE && ref->status != REF_STATUS_OK) n += print_one_push_status(ref, dest, n, porcelain); + if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) + *nonfastforward = 1; } } @@ -892,6 +896,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, @@ -997,7 +1002,8 @@ int transport_set_option(struct transport *transport, } int transport_push(struct transport *transport, - int refspec_nr, const char **refspec, int flags) + int refspec_nr, const char **refspec, int flags, + int * nonfastforward) { verify_remote_names(refspec_nr, refspec); @@ -1009,6 +1015,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; @@ -1024,7 +1031,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); + 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; @@ -1049,11 +1059,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; @@ -1061,6 +1072,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 51b539778c..171a01c7a3 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 *); @@ -68,7 +69,8 @@ int transport_set_option(struct transport *transport, const char *name, const char *value); int transport_push(struct transport *connection, - int refspec_nr, const char **refspec, int flags); + int refspec_nr, const char **refspec, int flags, + int * nonfastforward); const struct ref *transport_get_remote_refs(struct transport *transport); diff --git a/update-server-info.c b/update-server-info.c deleted file mode 100644 index 7b38fd867b..0000000000 --- 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 f7d308a411..4d8be834ff 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -651,6 +651,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]; diff --git a/wt-status.c b/wt-status.c index 47735d8129..63598ce40c 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,16 +30,35 @@ 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 (!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 (!s->is_initial) { color_fprintf_ln(s->fp, c, "# (use \"git reset %s <file>...\" to unstage)", s->reference); @@ -73,7 +71,7 @@ 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 (!has_deleted) color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to update what will be committed)"); @@ -85,7 +83,7 @@ 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:"); color_fprintf_ln(s->fp, c, "# (use \"git add <file>...\" to include in what will be committed)"); color_fprintf_ln(s->fp, c, "#"); @@ -93,23 +91,63 @@ static void wt_status_print_untracked_header(struct wt_status *s) 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; + } - color_fprintf(s->fp, color(WT_STATUS_HEADER), "#\t"); - switch (p->status) { + 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, s), "#\t"); + switch (status) { case DIFF_STATUS_ADDED: color_fprintf(s->fp, c, "new file: %s", one); break; @@ -135,64 +173,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 +288,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 +456,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 +471,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 +526,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 +544,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_unmerged(s); wt_status_print_updated(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 +580,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 78add09bd6..a0e75177be 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 */ diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 04ad468702..bc12f29895 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -190,48 +190,66 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) { int i1, i2; + if (!(flags & XDF_WHITESPACE_FLAGS)) + return s1 == s2 && !memcmp(l1, l2, s1); + + i1 = 0; + i2 = 0; + + /* + * -w matches everything that matches with -b, and -b in turn + * matches everything that matches with --ignore-space-at-eol. + * + * Each flavor of ignoring needs different logic to skip whitespaces + * while we have both sides to compare. + */ if (flags & XDF_IGNORE_WHITESPACE) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { - if (isspace(l1[i1])) - while (isspace(l1[i1]) && i1 < s1) - i1++; - if (isspace(l2[i2])) - while (isspace(l2[i2]) && i2 < s2) - i2++; - if (i1 < s1 && i2 < s2 && l1[i1++] != l2[i2++]) + goto skip_ws; + while (i1 < s1 && i2 < s2) { + if (l1[i1++] != l2[i2++]) return 0; + skip_ws: + while (i1 < s1 && isspace(l1[i1])) + i1++; + while (i2 < s2 && isspace(l2[i2])) + i2++; } - return (i1 >= s1 && i2 >= s2); } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { - if (isspace(l1[i1])) { - if (!isspace(l2[i2])) - return 0; - while (isspace(l1[i1]) && i1 < s1) - i1++; - while (isspace(l2[i2]) && i2 < s2) - i2++; - } else if (l1[i1++] != l2[i2++]) - return 0; - } - return (i1 >= s1 && i2 >= s2); - } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { - for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) { - if (l1[i1] != l2[i2]) { + while (i1 < s1 && i2 < s2) { + if (isspace(l1[i1]) && isspace(l2[i2])) { + /* Skip matching spaces and try again */ while (i1 < s1 && isspace(l1[i1])) i1++; while (i2 < s2 && isspace(l2[i2])) i2++; - if (i1 < s1 || i2 < s2) - return 0; - return 1; + continue; } + if (l1[i1++] != l2[i2++]) + return 0; + } + } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) { + while (i1 < s1 && i2 < s2 && l1[i1++] == l2[i2++]) + ; /* keep going */ + } + + /* + * After running out of one side, the remaining side must have + * nothing but whitespace for the lines to match. Note that + * ignore-whitespace-at-eol case may break out of the loop + * while there still are characters remaining on both lines. + */ + if (i1 < s1) { + while (i1 < s1 && isspace(l1[i1])) i1++; + if (s1 != i1) + return 0; + } + if (i2 < s2) { + while (i2 < s2 && isspace(l2[i2])) i2++; - } - return i1 >= s1 && i2 >= s2; - } else - return s1 == s2 && !memcmp(l1, l2, s1); + return (s2 == i2); + } + return 1; } static unsigned long xdl_hash_record_with_whitespace(char const **data, @@ -242,18 +260,20 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, for (; ptr < top && *ptr != '\n'; ptr++) { if (isspace(*ptr)) { const char *ptr2 = ptr; + int at_eol; while (ptr + 1 < top && isspace(ptr[1]) && ptr[1] != '\n') ptr++; + at_eol = (top <= ptr + 1 || ptr[1] == '\n'); if (flags & XDF_IGNORE_WHITESPACE) ; /* already handled */ else if (flags & XDF_IGNORE_WHITESPACE_CHANGE - && ptr[1] != '\n') { + && !at_eol) { ha += (ha << 5); ha ^= (unsigned long) ' '; } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL - && ptr[1] != '\n') { + && !at_eol) { while (ptr2 != ptr + 1) { ha += (ha << 5); ha ^= (unsigned long) *ptr2; |