diff options
165 files changed, 4262 insertions, 1041 deletions
diff --git a/.gitignore b/.gitignore index d9adce585..1c57d4c95 100644 --- a/.gitignore +++ b/.gitignore @@ -144,6 +144,7 @@ git-core-*/?* gitk-wish gitweb/gitweb.cgi test-chmtime +test-ctype test-date test-delta test-dump-cache-tree @@ -152,6 +153,7 @@ test-match-trees test-parse-options test-path-utils test-sha1 +test-sigchain common-cmds.h *.tar.gz *.dsc diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index f628c1f3b..0d7fa9cca 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -21,8 +21,13 @@ code. For git in general, three rough rules are: As for more concrete guidelines, just imitate the existing code (this is a good guideline, no matter which project you are -contributing to). But if you must have a list of rules, -here they are. +contributing to). It is always preferable to match the _local_ +convention. New code added to git suite is expected to match +the overall style of existing code. Modifications to existing +code is expected to match the style the surrounding code already +uses (even if it doesn't match the overall style of existing code). + +But if you must have a list of rules, here they are. For shell scripts specifically (not exhaustive): diff --git a/Documentation/RelNotes-1.6.1.1.txt b/Documentation/RelNotes-1.6.1.1.txt index 5cd1ca9cc..88454c197 100644 --- a/Documentation/RelNotes-1.6.1.1.txt +++ b/Documentation/RelNotes-1.6.1.1.txt @@ -4,9 +4,14 @@ GIT v1.6.1.1 Release Notes Fixes since v1.6.1 ------------------ +* "git add frotz/nitfol" when "frotz" is a submodule should have errored + out, but it didn't. + * "git apply" took file modes from the patch text and updated the mode bits of the target tree even when the patch was not about mode changes. +* "git bisect view" on Cygwin did not launch gitk + * "git checkout $tree" did not trigger an error. * "git commit" tried to remove COMMIT_EDITMSG from the work tree by mistake. @@ -14,6 +19,12 @@ Fixes since v1.6.1 * "git describe --all" complained when a commit is described with a tag, which was nonsense. +* "git diff --no-index --" did not trigger no-index (aka "use git-diff as + a replacement of diff on untracked files") behaviour. + +* "git format-patch -1 HEAD" on a root commit failed to produce patch + text. + * "git fsck branch" did not work as advertised; instead it behaved the same way as "git fsck". @@ -36,14 +47,13 @@ Fixes since v1.6.1 * "git mv -k" with more than one errorneous paths misbehaved. +* "git read-tree -m -u" hence branch switching incorrectly lost a + subdirectory in rare cases. + * "git rebase -i" issued an unnecessary error message upon a user error of marking the first commit to be "squash"ed. -Other documentation updates. - ---- -exec >/var/tmp/1 -O=v1.6.1-60-g78f111e -echo O=$(git describe maint) -git shortlog --no-merges $O..maint +* "git shortlog" did not format a commit message with multi-line + subject correctly. +Many documentation updates. diff --git a/Documentation/RelNotes-1.6.1.2.txt b/Documentation/RelNotes-1.6.1.2.txt new file mode 100644 index 000000000..230aa3d8e --- /dev/null +++ b/Documentation/RelNotes-1.6.1.2.txt @@ -0,0 +1,39 @@ +GIT v1.6.1.2 Release Notes +========================== + +Fixes since v1.6.1.1 +-------------------- + +* The logic for rename detectin in internal diff used by commands like + "git diff" and "git blame" have been optimized to avoid loading the same + blob repeatedly. + +* We did not allow writing out a blob that is larger than 2GB for no good + reason. + +* "git format-patch -o $dir", when $dir is a relative directory, used it + as relative to the root of the work tree, not relative to the current + directory. + +* v1.6.1 introduced an optimization for "git push" into a repository (A) + that borrows its objects from another repository (B) to avoid sending + objects that are available in repository B, when they are not yet used + by repository A. However the code on the "git push" sender side was + buggy and did not work when repository B had new objects that are not + known by the sender. This caused pushing into a "forked" repository + served by v1.6.1 software using "git push" from v1.6.1 sometimes did not + work. The bug was purely on the "git push" sender side, and has been + corrected. + +* "git status -v" did not paint its diff output in colour even when + color.ui configuration was set. + +* "git ls-tree" learned --full-tree option to help Porcelain scripts that + want to always see the full path regardless of the current working + directory. + +* "git grep" incorrectly searched in work tree paths even when they are + marked as assume-unchanged. It now searches in the index entries. + +* "git gc" with no grace period needlessly ejected packed but unreachable + objects in their loose form, only to delete them right away. diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt index 296804301..3151c85d8 100644 --- a/Documentation/RelNotes-1.6.2.txt +++ b/Documentation/RelNotes-1.6.2.txt @@ -6,6 +6,11 @@ Updates since v1.6.1 (subsystems) +* git-svn updates. + +* gitweb updates, including a new patch view and RSS/Atom feed + improvements. + (portability) (performance) @@ -15,25 +20,63 @@ Updates since v1.6.1 (usability, bells and whistles) -* "git-add -p" learned 'g'oto action to jump directly to a hunk. +* automatic typo correction works on aliases as well + +* @{-1} is a way to refer to the last branch you were on. This is + accepted not only where an object name is expected, but anywhere + a branch name is expected. E.g. "git branch --track mybranch @{-1}" + "git rev-parse --symbolic-full-name @{-1}". + +* "git add -p" learned 'g'oto action to jump directly to a hunk. + +* when "git am" stops upon a patch that does not apply, it shows the + title of the offending patch. + +* "git am --directory=<dir>" and "git am --reject" passes these options + to underlying "git apply". + +* "git clone" now makes its best effort when cloning from an empty + repository to set up configuration variables to refer to the remote + repository. + +* "git checkout -" is a shorthand for "git checkout @{-1}". -* git-cherry defaults to HEAD when the <upstream> argument is not given. +* "git cherry" defaults to whatever the current branch is tracking (if + exists) when the <upstream> argument is not given. -* git-cvsserver can be told not to add extra "via git-CVS emulator" to the - commit log message it serves via gitcvs.commitmsgannotation configuration. +* "git cvsserver" can be told not to add extra "via git-CVS emulator" to + the commit log message it serves via gitcvs.commitmsgannotation + configuration. -* git-diff learned a new option --inter-hunk-context to coalesce close +* "git diff" learned a new option --inter-hunk-context to coalesce close hunks together and show context between them. -* git-filter-branch learned --prune-empty option that discards commits +* The definition of what constitutes a word for "git diff --color-words" + can be customized via gitattributes, command line or a configuration. + +* "git diff" learned --patience to run "patience diff" algorithm. + +* Some combinations of -b/-w/--ignore-space-at-eol to "git diff" did + not work as expected. + +* "git filter-branch" learned --prune-empty option that discards commits that do not change the contents. -* git-ls-tree learned --full-tree option that shows the path in full +* "git grep -w" and "git grep" for fixed strings have been optimized. + +* "git log" and friends include HEAD to the set of starting points + when --all is given. This makes a difference when you are not on + any branch. + +* "git ls-tree" learned --full-tree option that shows the path in full regardless of where in the work tree hierarchy the command was started. -* git-mergetool learned -y(--no-prompt) option to disable prompting. +* "git mergetool" learned -y(--no-prompt) option to disable prompting. + +* "git rebase -i" can transplant a history down to root to elsewhere + with --root option. -* "git-reset --merge" is a new mode that works similar to the way +* "git reset --merge" is a new mode that works similar to the way "git checkout" switches branches, taking the local changes while switching to another commit. @@ -52,14 +95,12 @@ release, unless otherwise noted. * git-bundle did not exclude annotated tags even when a range given from the command line wanted to. -* git-grep did not work correctly for index entries with assume-unchanged bit. - * branch switching and merges had a silly bug that did not validate the correct directory when making sure an existing subdirectory is clean. -- exec >/var/tmp/1 -O=v1.6.1-134-ge98c6a1 +O=v1.6.1.2-252-g8c95d3c echo O=$(git describe master) git shortlog --no-merges $O..master ^maint diff --git a/Documentation/config.txt b/Documentation/config.txt index 290cb48eb..e2b8775dd 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -639,6 +639,12 @@ diff.suppressBlankEmpty:: A boolean to inhibit the standard behavior of printing a space before each empty output line. Defaults to false. +diff.wordRegex:: + A POSIX Extended Regular Expression used to determine what is a "word" + when performing word-by-word difference calculations. Character + sequences that match the regular expression are "words", all other + characters are *ignorable* whitespace. + fetch.unpackLimit:: If the number of objects fetched over the git native transfer is below this diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 43793d750..813a7b11b 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -36,6 +36,9 @@ endif::git-format-patch[] --patch-with-raw:: Synonym for "-p --raw". +--patience:: + Generate a diff using the "patience diff" algorithm. + --stat[=width[,name-width]]:: Generate a diffstat. You can override the default output width for 80-column terminal by "--stat=width". @@ -91,8 +94,22 @@ endif::git-format-patch[] Turn off colored diff, even when the configuration file gives the default to color output. ---color-words:: - Show colored word diff, i.e. color words which have changed. +--color-words[=<regex>]:: + Show colored word diff, i.e., color words which have changed. + By default, words are separated by whitespace. ++ +When a <regex> is specified, every non-overlapping match of the +<regex> is considered a word. Anything between these matches is +considered whitespace and ignored(!) for the purposes of finding +differences. You may want to append `|[^[:space:]]` to your regular +expression to make sure that it matches all non-whitespace characters. +A match that contains a newline is silently truncated(!) at the +newline. ++ +The regex can also be set via a diff driver or configuration option, see +linkgit:gitattributes[1] or linkgit:git-config[1]. Giving it explicitly +overrides any diff driver or configuration setting. Diff drivers +override configuration settings. --no-renames:: Turn off rename detection, even when the configuration diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index efd311b1c..ff307eb27 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -10,7 +10,8 @@ SYNOPSIS -------- [verse] 'git am' [--signoff] [--keep] [--utf8 | --no-utf8] - [--3way] [--interactive] + [--3way] [--interactive] [--committer-date-is-author-date] + [--ignore-date] [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] [--reject] [<mbox> | <Maildir>...] @@ -73,6 +74,20 @@ default. You could use `--no-utf8` to override this. --interactive:: Run interactively. +--committer-date-is-author-date:: + By default the command records the date from the e-mail + message as the commit author date, and uses the time of + commit creation as the committer date. This allows the + user to lie about the committer date by using the same + timestamp as the author date. + +--ignore-date:: + By default the command records the date from the e-mail + message as the commit author date, and uses the time of + commit creation as the committer date. This allows the + user to lie about author timestamp by using the same + timestamp as the committer date. + --skip:: Skip the current patch. This is only meaningful when restarting an aborted patch. diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 1b66ab743..ea0f6a0f3 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -84,7 +84,7 @@ defining the basis. More than one reference may be packaged, and more than one basis can be specified. The objects packaged are those not contained in the union of the given bases. Each basis can be specified explicitly (e.g., ^master~10), or implicitly (e.g., -master~10..master, master --since=10.days.ago). +master~10..master, --since=10.days.ago master). It is very important that the basis used be held by the destination. It is okay to err on the side of conservatism, causing the bundle file @@ -94,75 +94,111 @@ when unpacking at the destination. EXAMPLE ------- -Assume two repositories exist as R1 on machine A, and R2 on machine B. +Assume you want to transfer the history from a repository R1 on machine A +to another repository R2 on machine B. For whatever reason, direct connection between A and B is not allowed, but we can move data from A to B via some mechanism (CD, email, etc). We want to update R2 with developments made on branch master in R1. -To create the bundle you have to specify the basis. You have some options: +To bootstrap the process, you can first create a bundle that doesn't have +any basis. You can use a tag to remember up to what commit you sent out +in order to make it easy to later update the other repository with +incremental bundle, -- Without basis. -+ -This is useful when sending the whole history. +---------------- +machineA$ cd R1 +machineA$ git bundle create file.bdl master +machineA$ git tag -f lastR2bundle master +---------------- ------------- -$ git bundle create mybundle master ------------- +Then you sneakernet file.bdl to the target machine B. Because you don't +have to have any object to extract objects from such a bundle, not only +you can fetch/pull from a bundle, you can clone from it as if it was a +remote repository. -- Using temporally tags. -+ -We set a tag in R1 (lastR2bundle) after the previous such transport, -and move it afterwards to help build the bundle. +---------------- +machineB$ git clone /home/me/tmp/file.bdl R2 +---------------- ------------- -$ git bundle create mybundle master ^lastR2bundle -$ git tag -f lastR2bundle master ------------- +This will define a remote called "origin" in the resulting repository that +lets you fetch and pull from the bundle. $GIT_DIR/config file in R2 may +have an entry like this: -- Using a tag present in both repositories +------------------------ +[remote "origin"] + url = /home/me/tmp/file.bdl + fetch = refs/heads/*:refs/remotes/origin/* +------------------------ + +You can fetch/pull to update the resulting mine.git repository after +replacing the bundle you store at /home/me/tmp/file.bdl with incremental +updates from here on. + +After working more in the original repository, you can create an +incremental bundle to update the other: + +---------------- +machineA$ cd R1 +machineA$ git bundle create file.bdl lastR2bundle..master +machineA$ git tag -f lastR2bundle master +---------------- + +and sneakernet it to the other machine to replace /home/me/tmp/file.bdl, +and pull from it. + +---------------- +machineB$ cd R2 +machineB$ git pull +---------------- ------------- -$ git bundle create mybundle master ^v1.0.0 ------------- +If you know up to what commit the intended recipient repository should +have the necessary objects for, you can use that knowledge to specify the +basis, giving a cut-off point to limit the revisions and objects that go +in the resulting bundle. The previous example used lastR2bundle tag +for this purpose, but you can use other options you would give to +the linkgit:git-log[1] command. Here are more examples: -- A basis based on time. +You can use a tag that is present in both. ------------- -$ git bundle create mybundle master --since=10.days.ago ------------- +---------------- +$ git bundle create mybundle v1.0.0..master +---------------- -- With a limit on the number of commits +You can use a basis based on time. ------------- -$ git bundle create mybundle master -n 10 ------------- +---------------- +$ git bundle create mybundle --since=10.days master +---------------- -Then you move mybundle from A to B, and in R2 on B: +Or you can use the number of commits. ------------- +---------------- +$ git bundle create mybundle -10 master +---------------- + +You can run `git-bundle verify` to see if you can extract from a bundle +that was created with a basis. + +---------------- $ git bundle verify mybundle -$ git fetch mybundle master:localRef ------------- +---------------- -With something like this in the config in R2: +This will list what commits you must have in order to extract from the +bundle and will error out if you don't have them. ------------------------- -[remote "bundle"] - url = /home/me/tmp/file.bdl - fetch = refs/heads/*:refs/remotes/origin/* ------------------------- +A bundle from a recipient repository's point of view is just like a +regular repository it fetches/pulls from. You can for example map +refs, like this example, when fetching: -You can first sneakernet the bundle file to ~/tmp/file.bdl and -then these commands on machine B: +---------------- +$ git fetch mybundle master:localRef +---------------- ------------- -$ git ls-remote bundle -$ git fetch bundle -$ git pull bundle ------------- +Or see what refs it offers. -would treat it as if it is talking with a remote side over the -network. +---------------- +$ git ls-remote mybundle +---------------- Author ------ diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 9cd51514d..3bccffae6 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -133,6 +133,10 @@ the conflicted merge in the specified paths. + When this parameter names a non-branch (but still a valid commit object), your HEAD becomes 'detached'. ++ +As a special case, the "`@\{-N\}`" syntax for the N-th last branch +checks out the branch (instead of detaching). You may also specify +"`-`" which is synonymous with "`@\{-1\}`". Detached HEAD diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index a99b4ef94..b231dbb94 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -43,7 +43,7 @@ OPTIONS Automatically implies --tags. --abbrev=<n>:: - Instead of using the default 8 hexadecimal digits as the + Instead of using the default 7 hexadecimal digits as the abbreviated object name, use <n> digits. --candidates=<n>:: diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 7b27dc60b..7d1eced7d 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -33,25 +33,27 @@ OPTIONS of a remote (see the section <<REMOTES,REMOTES>> below). <refspec>...:: - The canonical format of a <refspec> parameter is - `+?<src>:<dst>`; that is, an optional plus `{plus}`, followed - by the source ref, followed by a colon `:`, followed by - the destination ref. + The format of a <refspec> parameter is an optional plus + `{plus}`, followed by the source ref <src>, followed + by a colon `:`, followed by the destination ref <dst>. + It is used to specify with what <src> object the <dst> ref + in the remote repository is to be updated. + -The <src> side represents the source branch (or arbitrary -"SHA1 expression", such as `master~4` (four parents before the -tip of `master` branch); see linkgit:git-rev-parse[1]) that you -want to push. The <dst> side represents the destination location. +The <src> is often the name of the branch you would want to push, but +it can be any arbitrary "SHA-1 expression", such as `master~4` or +`HEAD` (see linkgit:git-rev-parse[1]). + -The local ref that matches <src> is used -to fast forward the remote ref that matches <dst>. If -the optional leading plus `+` is used, the remote ref is updated -even if it does not result in a fast forward update. +The <dst> tells which ref on the remote side is updated with this +push. Arbitrary expressions cannot be used here, an actual ref must +be named. If `:`<dst> is omitted, the same ref as <src> will be +updated. + -`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. +The object referenced by <src> is used to fast forward the ref <dst> +on the remote side. If the optional leading plus `{plus}` is used, the +remote ref is updated even if it does not result in a fast forward +update. + -A lonely <src> parameter (without a colon and a destination) pushes -the <src> to the same name in the destination repository. +`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. + Pushing an empty <src> allows you to delete the <dst> ref from the remote repository. @@ -190,9 +192,9 @@ git push origin master:: with it. If `master` did not exist remotely, it would be created. -git push origin :experimental:: - Find a ref that matches `experimental` in the `origin` repository - (e.g. `refs/heads/experimental`), and delete it. +git push origin HEAD:: + A handy way to push the current branch to the same name on the + remote. git push origin master:satellite/master dev:satellite/dev:: Use the source ref that matches `master` (e.g. `refs/heads/master`) @@ -200,6 +202,11 @@ git push origin master:satellite/master dev:satellite/dev:: `refs/remotes/satellite/master`) in the `origin` repository, then do the same for `dev` and `satellite/dev`. +git push origin HEAD:master:: + Push the current branch to the remote ref matching `master` in the + `origin` repository. This form is convenient to push the current + branch without thinking about its local name. + git push origin master:refs/heads/experimental:: Create the branch `experimental` in the `origin` repository by copying the current `master` branch. This form is only @@ -207,6 +214,11 @@ git push origin master:refs/heads/experimental:: the local name and the remote name are different; otherwise, the ref name on its own will work. +git push origin :experimental:: + Find a ref that matches `experimental` in the `origin` repository + (e.g. `refs/heads/experimental`), and delete it. + + Author ------ Written by Junio C Hamano <gitster@pobox.com>, later rewritten in C diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 2921da320..3ccef2f2b 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -212,6 +212,9 @@ when you run 'git-merge'. reflog of the current branch. For example, if you are on the branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'. +* The special construct '@\{-<n>\}' means the <n>th branch checked out + before the current one. + * A suffix '{caret}' to a revision parameter means the first parent of that commit object. '{caret}<n>' means the <n>th parent (i.e. 'rev{caret}' diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 8f7c0e226..498bd2892 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -82,7 +82,7 @@ her family name fully spelled out, a proper `.mailmap` file would look like: # Note how we don't need an entry for <jane@laptop.(none)>, because the # real name of that author is correct already, and coalesced directly. Jane Doe <jane@desktop.(none)> -Joe R. Developer <joe@random.com> +Joe R. Developer <joe@example.com> ------------ Author diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 8277577a6..7e9ff3762 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -99,12 +99,12 @@ OPTIONS will show the revisions given by "git rev-list {caret}master topic1 topic2" +-g:: --reflog[=<n>[,<base>]] [<ref>]:: Shows <n> most recent ref-log entries for the given ref. If <base> is given, <n> entries going back from that entry. <base> can be specified as count or date. - `-g` can be used as a short-hand for this option. When - no explicit <ref> parameter is given, it defaults to the + When no explicit <ref> parameter is given, it defaults to the current branch (or `HEAD` if it is detached). Note that --more, --list, --independent and --merge-base options diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 63d2f5e96..7b654f792 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -103,6 +103,19 @@ 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. +--ignore-paths=<regex>;; + This allows one to specify Perl regular expression that will + cause skipping of all matching paths from checkout from SVN. + Examples: + + --ignore-paths="^doc" - skip "doc*" directory for every fetch. + + --ignore-paths="^[^/]+/(?:branches|tags)" - skip "branches" + and "tags" of first level directories. + + Regular expression is not persistent, you should specify + it every time when fetching. + 'clone':: Runs 'init' and 'fetch'. It will automatically create a directory based on the basename of the URL passed to it; diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index e44f54302..533d18bbd 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] <name> [<commit> | <object>] 'git tag' -d <name>... -'git tag' [-n[<num>]] -l [<pattern>] +'git tag' [-n[<num>]] -l [--contains <commit>] [<pattern>] 'git tag' -v <name>... DESCRIPTION @@ -68,6 +68,9 @@ OPTIONS List tags with names that match the given pattern (or all if no pattern is given). Typing "git tag" without arguments, also lists all tags. +--contains <commit>:: + Only list tags which contain the specified commit. + -m <msg>:: Use the given tag message (instead of prompting). If multiple `-m` options are given, their values are diff --git a/Documentation/git.txt b/Documentation/git.txt index 17dc8b201..cd527c625 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: -* link:v1.6.1/git.html[documentation for release 1.6.1] +* link:v1.6.1.1/git.html[documentation for release 1.6.1.1] * release notes for + link:RelNotes-1.6.1.1.txt[1.6.1.1], link:RelNotes-1.6.1.txt[1.6.1]. * link:v1.6.0.6/git.html[documentation for release 1.6.0.6] diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 8af22ecca..227934f59 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -317,6 +317,8 @@ patterns are available: - `bibtex` suitable for files with BibTeX coded references. +- `cpp` suitable for source code in the C and C++ languages. + - `html` suitable for HTML/XHTML documents. - `java` suitable for source code in the Java language. @@ -334,6 +336,25 @@ patterns are available: - `tex` suitable for source code for LaTeX documents. +Customizing word diff +^^^^^^^^^^^^^^^^^^^^^ + +You can customize the rules that `git diff --color-words` uses to +split words in a line, by specifying an appropriate regular expression +in the "diff.*.wordRegex" configuration variable. For example, in TeX +a backslash followed by a sequence of letters forms a command, but +several such commands can be run together without intervening +whitespace. To separate them, use a regular expression such as + +------------------------ +[diff "tex"] + wordRegex = "\\\\[a-zA-Z]+|[{}]|\\\\.|[^\\{}[:space:]]+" +------------------------ + +A built-in pattern is provided for all languages listed in the +previous section. + + Performing text diffs of binary files ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 458fafdb2..c5d5596d8 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -308,9 +308,7 @@ alice$ git pull /home/bob/myrepo master This merges the changes from Bob's "master" branch into Alice's current branch. If Alice has made her own changes in the meantime, -then she may need to manually fix any conflicts. (Note that the -"master" argument in the above command is actually unnecessary, as it -is the default.) +then she may need to manually fix any conflicts. The "pull" command thus performs two operations: it fetches changes from a remote branch, then merges them into the current branch. diff --git a/Documentation/pull-fetch-param.txt b/Documentation/pull-fetch-param.txt index ebdd948cd..f9811f247 100644 --- a/Documentation/pull-fetch-param.txt +++ b/Documentation/pull-fetch-param.txt @@ -5,15 +5,14 @@ of a remote (see the section <<REMOTES,REMOTES>> below). <refspec>:: - The canonical format of a <refspec> parameter is - `+?<src>:<dst>`; that is, an optional plus `{plus}`, followed - by the source ref, followed by a colon `:`, followed by - the destination ref. + The format of a <refspec> parameter is an optional plus + `{plus}`, followed by the source ref <src>, followed + by a colon `:`, followed by the destination ref <dst>. + The remote ref that matches <src> is fetched, and if <dst> is not empty string, the local ref that matches it is fast forwarded using <src>. -Again, if the optional plus `+` is used, the local ref +If the optional plus `+` is used, the local ref is updated even if it does not result in a fast forward update. + diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 9a4e3ea92..ac56d1c47 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -21,7 +21,7 @@ allocated memory or not), use `strbuf_detach()` to unwrap a memory buffer from its strbuf shell in a safe way. That is the sole supported way. This will give you a malloced buffer that you can later `free()`. + -However, it it totally safe to modify anything in the string pointed by +However, it is totally safe to modify anything in the string pointed by the `buf` member, between the indices `0` and `len-1` (inclusive). . The `buf` member is a byte array that has at least `len + 1` bytes diff --git a/Documentation/urls.txt b/Documentation/urls.txt index fa34c6747..5355ebc0f 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -6,10 +6,10 @@ to name the remote repository: =============================================================== - rsync://host.xz/path/to/repo.git/ -- http://host.xz/path/to/repo.git/ -- https://host.xz/path/to/repo.git/ -- git://host.xz/path/to/repo.git/ -- git://host.xz/~user/path/to/repo.git/ +- http://host.xz{startsb}:port{endsb}/path/to/repo.git/ +- https://host.xz{startsb}:port{endsb}/path/to/repo.git/ +- git://host.xz{startsb}:port{endsb}/path/to/repo.git/ +- git://host.xz{startsb}:port{endsb}/~user/path/to/repo.git/ - ssh://{startsb}user@{endsb}host.xz{startsb}:port{endsb}/path/to/repo.git/ - ssh://{startsb}user@{endsb}host.xz/path/to/repo.git/ - ssh://{startsb}user@{endsb}host.xz/~user/path/to/repo.git/ diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 19f571ae3..96af8977f 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -1507,7 +1507,7 @@ so on a different branch and then coming back), unstash the work-in-progress changes. ------------------------------------------------ -$ git stash "work in progress for foo feature" +$ git stash save "work in progress for foo feature" ------------------------------------------------ This command will save your changes away to the `stash`, and diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 6c7465c75..9c9fe640f 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.0.2.GIT +DEF_VER=v1.6.1.GIT LF=' ' @@ -23,6 +23,9 @@ all:: # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. # +# Define EXPATDIR=/foo/bar if your expat header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +# # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks @@ -179,28 +182,32 @@ STRIP ?= strip # Among the variables below, these: # gitexecdir # template_dir +# mandir +# infodir # htmldir # ETC_GITCONFIG (but not sysconfdir) -# can be specified as a relative path ../some/where/else (which must begin -# with ../); this is interpreted as relative to $(bindir) and "git" at +# can be specified as a relative path some/where/else; +# this is interpreted as relative to $(prefix) and "git" at # runtime figures out where they are based on the path to the executable. # This can help installing the suite in a relocatable way. prefix = $(HOME) -bindir = $(prefix)/bin -mandir = $(prefix)/share/man -infodir = $(prefix)/share/info -gitexecdir = $(prefix)/libexec/git-core +bindir_relative = bin +bindir = $(prefix)/$(bindir_relative) +mandir = share/man +infodir = share/info +gitexecdir = libexec/git-core sharedir = $(prefix)/share -template_dir = $(sharedir)/git-core/templates -htmldir=$(sharedir)/doc/git-doc +template_dir = share/git-core/templates +htmldir = share/doc/git-doc ifeq ($(prefix),/usr) sysconfdir = /etc +ETC_GITCONFIG = $(sysconfdir)/gitconfig else sysconfdir = $(prefix)/etc +ETC_GITCONFIG = etc/gitconfig endif lib = lib -ETC_GITCONFIG = $(sysconfdir)/gitconfig # DESTDIR= # default configuration for gitweb @@ -388,6 +395,7 @@ LIB_H += revision.h LIB_H += run-command.h LIB_H += sha1-lookup.h LIB_H += sideband.h +LIB_H += sigchain.h LIB_H += strbuf.h LIB_H += tag.h LIB_H += transport.h @@ -481,6 +489,7 @@ LIB_OBJS += sha1-lookup.o LIB_OBJS += sha1_name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o +LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o @@ -640,10 +649,12 @@ endif ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease - ifneq ($(shell expr "$(uname_R)" : '9\.'),2) + ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) OLD_ICONV = UnfortunatelyYes endif - NO_STRLCPY = YesPlease + ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) + NO_STRLCPY = YesPlease + endif NO_MEMMEM = YesPlease THREADED_DELTA_SEARCH = YesPlease endif @@ -785,6 +796,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) SNPRINTF_RETURNS_BOGUS = YesPlease NO_SVN_TESTS = YesPlease NO_PERL_MAKEMAKER = YesPlease + RUNTIME_PREFIX = YesPlease NO_POSIX_ONLY_PROGRAMS = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch @@ -793,9 +805,6 @@ ifneq (,$(findstring MINGW,$(uname_S))) COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o EXTLIBS += -lws2_32 X = .exe - gitexecdir = ../libexec/git-core - template_dir = ../share/git-core/templates/ - ETC_GITCONFIG = ../etc/gitconfig endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease @@ -817,6 +826,7 @@ ifeq ($(uname_S),Darwin) BASIC_LDFLAGS += -L/opt/local/lib endif endif + PTHREAD_LIBS = endif ifndef CC_LD_DYNPATH @@ -849,7 +859,12 @@ else endif endif ifndef NO_EXPAT - EXPAT_LIBEXPAT = -lexpat + ifdef EXPATDIR + BASIC_CFLAGS += -I$(EXPATDIR)/include + EXPAT_LIBEXPAT = -L$(EXPATDIR)/$(lib) $(CC_LD_DYNPATH)$(EXPATDIR)/$(lib) -lexpat + else + EXPAT_LIBEXPAT = -lexpat + endif endif endif @@ -1027,6 +1042,9 @@ ifdef INTERNAL_QSORT COMPAT_CFLAGS += -DINTERNAL_QSORT COMPAT_OBJS += compat/qsort.o endif +ifdef RUNTIME_PREFIX + COMPAT_CFLAGS += -DRUNTIME_PREFIX +endif ifdef NO_PTHREADS THREADED_DELTA_SEARCH = @@ -1086,6 +1104,7 @@ ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG)) DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) bindir_SQ = $(subst ','\'',$(bindir)) +bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) mandir_SQ = $(subst ','\'',$(mandir)) infodir_SQ = $(subst ','\'',$(infodir)) gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) @@ -1251,7 +1270,12 @@ git.o git.spec \ $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $< exec_cmd.o: exec_cmd.c GIT-CFLAGS - $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $< + $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \ + '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' \ + '-DBINDIR="$(bindir_relative_SQ)"' \ + '-DPREFIX="$(prefix_SQ)"' \ + $< + builtin-init-db.o: builtin-init-db.c GIT-CFLAGS $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $< @@ -1287,7 +1311,7 @@ $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS) XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ - xdiff/xmerge.o + xdiff/xmerge.o xdiff/xpatience.o $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h @@ -1360,11 +1384,13 @@ TEST_PROGRAMS += test-chmtime$X TEST_PROGRAMS += test-ctype$X TEST_PROGRAMS += test-date$X TEST_PROGRAMS += test-delta$X +TEST_PROGRAMS += test-dump-cache-tree$X TEST_PROGRAMS += test-genrandom$X TEST_PROGRAMS += test-match-trees$X TEST_PROGRAMS += test-parse-options$X TEST_PROGRAMS += test-path-utils$X TEST_PROGRAMS += test-sha1$X +TEST_PROGRAMS += test-sigchain$X all:: $(TEST_PROGRAMS) @@ -1410,17 +1436,17 @@ remove-dashes: ### Installation rules -ifeq ($(firstword $(subst /, ,$(template_dir))),..) -template_instdir = $(bindir)/$(template_dir) -else +ifneq ($(filter /%,$(firstword $(template_dir))),) template_instdir = $(template_dir) +else +template_instdir = $(prefix)/$(template_dir) endif export template_instdir -ifeq ($(firstword $(subst /, ,$(gitexecdir))),..) -gitexec_instdir = $(bindir)/$(gitexecdir) -else +ifneq ($(filter /%,$(firstword $(gitexecdir))),) gitexec_instdir = $(gitexecdir) +else +gitexec_instdir = $(prefix)/$(gitexecdir) endif gitexec_instdir_SQ = $(subst ','\'',$(gitexec_instdir)) export gitexec_instdir diff --git a/builtin-apply.c b/builtin-apply.c index b415daf07..f312798af 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2441,7 +2441,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s return error("%s: %s", old_name, strerror(errno)); } - if (!cached) + if (!cached && !tpatch) st_mode = ce_mode_from_stat(*ce, st->st_mode); if (patch->is_new < 0) @@ -2453,7 +2453,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s if (st_mode != patch->old_mode) fprintf(stderr, "warning: %s has type %o, expected %o\n", old_name, st_mode, patch->old_mode); - if (!patch->new_mode) + if (!patch->new_mode && !patch->is_delete) patch->new_mode = st_mode; return 0; diff --git a/builtin-branch.c b/builtin-branch.c index 02fa38fd3..56a1971d6 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -193,21 +193,6 @@ struct ref_list { int kinds; }; -static int has_commit(struct commit *commit, struct commit_list *with_commit) -{ - if (!with_commit) - return 1; - while (with_commit) { - struct commit *other; - - other = with_commit->item; - with_commit = with_commit->next; - if (in_merge_bases(other, &commit, 1)) - return 1; - } - return 0; -} - static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct ref_list *ref_list = (struct ref_list*)(cb_data); @@ -231,7 +216,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, return error("branch '%s' does not point at a commit", refname); /* Filter with with_commit if specified */ - if (!has_commit(commit, ref_list->with_commit)) + if (!is_descendant_of(commit, ref_list->with_commit)) return 0; /* Don't add types the caller doesn't want */ @@ -401,7 +386,8 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); detached = (detached && (kinds & REF_LOCAL_BRANCH)); - if (detached && head_commit && has_commit(head_commit, with_commit)) { + if (detached && head_commit && + is_descendant_of(head_commit, with_commit)) { struct ref_item item; item.name = xstrdup("(no branch)"); item.kind = REF_LOCAL_BRANCH; @@ -466,22 +452,6 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&newsection); } -static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset) -{ - unsigned char sha1[20]; - struct commit *commit; - - if (!arg) - return -1; - if (get_sha1(arg, sha1)) - die("malformed object name %s", arg); - commit = lookup_commit_reference(sha1); - if (!commit) - die("no such commit %s", arg); - commit_list_insert(commit, opt->value); - return 0; -} - static int opt_parse_merge_filter(const struct option *opt, const char *arg, int unset) { merge_filter = ((opt->long_name[0] == 'n') @@ -517,13 +487,13 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPTION_CALLBACK, 0, "contains", &with_commit, "commit", "print only branches that contain the commit", PARSE_OPT_LASTARG_DEFAULT, - opt_parse_with_commit, (intptr_t)"HEAD", + parse_opt_with_commit, (intptr_t)"HEAD", }, { OPTION_CALLBACK, 0, "with", &with_commit, "commit", "print only branches that contain the commit", PARSE_OPT_HIDDEN | PARSE_OPT_LASTARG_DEFAULT, - opt_parse_with_commit, (intptr_t) "HEAD", + parse_opt_with_commit, (intptr_t) "HEAD", }, OPT__ABBREV(&abbrev), diff --git a/builtin-checkout.c b/builtin-checkout.c index 275176d15..20b34ce6e 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -230,7 +230,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - pathspec_match(pathspec, ps_matched, ce->name, 0); + match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched); } if (report_path_error(ps_matched, pathspec, 0)) @@ -239,7 +239,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, /* Any unmerged paths? */ for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (pathspec_match(pathspec, NULL, ce->name, 0)) { + if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { if (!ce_stage(ce)) continue; if (opts->force) { @@ -264,7 +264,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, state.refresh_cache = 1; for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; - if (pathspec_match(pathspec, NULL, ce->name, 0)) { + if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, NULL); continue; @@ -351,8 +351,16 @@ struct branch_info { static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, "refs/heads/"); - strbuf_addstr(&buf, branch->name); + int ret; + + if ((ret = interpret_nth_last_branch(branch->name, &buf)) + && ret == strlen(branch->name)) { + branch->name = xstrdup(buf.buf); + strbuf_splice(&buf, 0, 0, "refs/heads/", 11); + } else { + strbuf_addstr(&buf, "refs/heads/"); + strbuf_addstr(&buf, branch->name); + } branch->path = strbuf_detach(&buf, NULL); } @@ -661,6 +669,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) arg = argv[0]; has_dash_dash = (argc > 1) && !strcmp(argv[1], "--"); + if (!strcmp(arg, "-")) + arg = "@{-1}"; + if (get_sha1(arg, rev)) { if (has_dash_dash) /* case (1) */ die("invalid reference: %s", arg); diff --git a/builtin-clone.c b/builtin-clone.c index f7e5a7b0a..f73029e2b 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -19,6 +19,7 @@ #include "strbuf.h" #include "dir.h" #include "pack-refs.h" +#include "sigchain.h" /* * Overall FIXMEs: @@ -288,7 +289,7 @@ static void remove_junk(void) static void remove_junk_on_signal(int signo) { remove_junk(); - signal(SIGINT, SIG_DFL); + sigchain_pop(signo); raise(signo); } @@ -441,7 +442,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } junk_git_dir = git_dir; atexit(remove_junk); - signal(SIGINT, remove_junk_on_signal); + sigchain_push_common(remove_junk_on_signal); setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1); @@ -522,14 +523,23 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_upload_pack); refs = transport_get_remote_refs(transport); - transport_fetch_refs(transport, refs); + if(refs) + transport_fetch_refs(transport, refs); } - clear_extra_refs(); + if (refs) { + clear_extra_refs(); - mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf); + mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf); - head_points_at = locate_head(refs, mapped_refs, &remote_head); + head_points_at = locate_head(refs, mapped_refs, &remote_head); + } + else { + warning("You appear to have cloned an empty repository."); + head_points_at = NULL; + remote_head = NULL; + option_no_checkout = 1; + } if (head_points_at) { /* Local default branch link */ diff --git a/builtin-commit.c b/builtin-commit.c index 7aaa5304c..d6a3a6203 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -166,7 +166,7 @@ static int list_paths(struct string_list *list, const char *with_tree, struct cache_entry *ce = active_cache[i]; if (ce->ce_flags & CE_UPDATE) continue; - if (!pathspec_match(pattern, m, ce->name, 0)) + if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m)) continue; string_list_insert(ce->name, list); } diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 469b07e24..29356d25d 100644 --- a/builtin-fetch--tool.c +++ b/builtin-fetch--tool.c @@ -2,6 +2,7 @@ #include "cache.h" #include "refs.h" #include "commit.h" +#include "sigchain.h" static char *get_stdin(void) { @@ -186,7 +187,7 @@ static void remove_keep(void) static void remove_keep_on_signal(int signo) { remove_keep(); - signal(SIGINT, SIG_DFL); + sigchain_pop(signo); raise(signo); } @@ -245,7 +246,7 @@ static int fetch_native_store(FILE *fp, char buffer[1024]; int err = 0; - signal(SIGINT, remove_keep_on_signal); + sigchain_push_common(remove_keep_on_signal); atexit(remove_keep); while (fgets(buffer, sizeof(buffer), stdin)) { diff --git a/builtin-fetch.c b/builtin-fetch.c index de6f3074b..1e4a3d9c5 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -10,6 +10,7 @@ #include "transport.h" #include "run-command.h" #include "parse-options.h" +#include "sigchain.h" static const char * const builtin_fetch_usage[] = { "git fetch [options] [<repository> <refspec>...]", @@ -58,7 +59,7 @@ static void unlock_pack(void) static void unlock_pack_on_signal(int signo) { unlock_pack(); - signal(SIGINT, SIG_DFL); + sigchain_pop(signo); raise(signo); } @@ -672,7 +673,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) ref_nr = j; } - signal(SIGINT, unlock_pack_on_signal); + sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack); exit_code = do_fetch(transport, parse_fetch_refspec(ref_nr, refs), ref_nr); diff --git a/builtin-fsck.c b/builtin-fsck.c index aecc8280a..64dffa542 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -23,6 +23,7 @@ static int check_full; static int check_strict; static int keep_cache_objects; static unsigned char head_sha1[20]; +static const char *head_points_at; static int errors_found; static int write_lost_and_found; static int verbose; @@ -473,6 +474,8 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f static void get_default_heads(void) { + if (head_points_at && !is_null_sha1(head_sha1)) + fsck_handle_ref("HEAD", head_sha1, 0, NULL); for_each_ref(fsck_handle_ref, NULL); if (include_reflogs) for_each_reflog(fsck_handle_reflog, NULL); @@ -512,14 +515,13 @@ static void fsck_object_dir(const char *path) static int fsck_head_link(void) { - unsigned char sha1[20]; int flag; int null_is_error = 0; - const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag); if (verbose) fprintf(stderr, "Checking HEAD link\n"); + head_points_at = resolve_ref("HEAD", head_sha1, 0, &flag); if (!head_points_at) return error("Invalid HEAD"); if (!strcmp(head_points_at, "HEAD")) @@ -528,7 +530,7 @@ static int fsck_head_link(void) else if (prefixcmp(head_points_at, "refs/heads/")) return error("HEAD points to something strange (%s)", head_points_at); - if (is_null_sha1(sha1)) { + if (is_null_sha1(head_sha1)) { if (null_is_error) return error("HEAD: detached HEAD points at nothing"); fprintf(stderr, "notice: HEAD points to an unborn branch (%s)\n", @@ -584,6 +586,7 @@ static struct option fsck_opts[] = { int cmd_fsck(int argc, const char **argv, const char *prefix) { int i, heads; + struct alternate_object_database *alt; errors_found = 0; @@ -595,17 +598,19 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) fsck_head_link(); fsck_object_dir(get_object_directory()); + + prepare_alt_odb(); + for (alt = alt_odb_list; alt; alt = alt->next) { + char namebuf[PATH_MAX]; + int namelen = alt->name - alt->base; + memcpy(namebuf, alt->base, namelen); + namebuf[namelen - 1] = 0; + fsck_object_dir(namebuf); + } + if (check_full) { - struct alternate_object_database *alt; struct packed_git *p; - prepare_alt_odb(); - for (alt = alt_odb_list; alt; alt = alt->next) { - char namebuf[PATH_MAX]; - int namelen = alt->name - alt->base; - memcpy(namebuf, alt->base, namelen); - namebuf[namelen - 1] = 0; - fsck_object_dir(namebuf); - } + prepare_packed_git(); for (p = packed_git; p; p = p->next) /* verify gives error messages itself */ @@ -624,8 +629,9 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) heads = 0; for (i = 0; i < argc; i++) { const char *arg = argv[i]; - if (!get_sha1(arg, head_sha1)) { - struct object *obj = lookup_object(head_sha1); + unsigned char sha1[20]; + if (!get_sha1(arg, sha1)) { + struct object *obj = lookup_object(sha1); /* Error is printed by lookup_object(). */ if (!obj) diff --git a/builtin-grep.c b/builtin-grep.c index bebf15cd6..3f12ba382 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -291,6 +291,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) push_arg("-E"); if (opt->regflags & REG_ICASE) push_arg("-i"); + if (opt->binary == GREP_BINARY_NOMATCH) + push_arg("-I"); if (opt->word_regexp) push_arg("-w"); if (opt->name_only) diff --git a/builtin-help.c b/builtin-help.c index f076efa92..9b57a7461 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -329,7 +329,7 @@ static void setup_man_path(void) * old_path, the ':' at the end will let 'man' to try * system-wide paths after ours to find the manual page. If * there is old_path, we need ':' as delimiter. */ - strbuf_addstr(&new_path, GIT_MAN_PATH); + strbuf_addstr(&new_path, system_path(GIT_MAN_PATH)); strbuf_addch(&new_path, ':'); if (old_path) strbuf_addstr(&new_path, old_path); @@ -375,7 +375,7 @@ static void show_man_page(const char *git_cmd) static void show_info_page(const char *git_cmd) { const char *page = cmd_to_page(git_cmd); - setenv("INFOPATH", GIT_INFO_PATH, 1); + setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); execlp("info", "info", "gitman", page, NULL); } diff --git a/builtin-ls-files.c b/builtin-ls-files.c index f72eb8547..343403129 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -36,42 +36,6 @@ static const char *tag_other = ""; static const char *tag_killed = ""; static const char *tag_modified = ""; - -/* - * Match a pathspec against a filename. The first "skiplen" characters - * are the common prefix - */ -int pathspec_match(const char **spec, char *ps_matched, - const char *filename, int skiplen) -{ - const char *m; - - while ((m = *spec++) != NULL) { - int matchlen = strlen(m + skiplen); - - if (!matchlen) - goto matched; - if (!strncmp(m + skiplen, filename + skiplen, matchlen)) { - if (m[skiplen + matchlen - 1] == '/') - goto matched; - switch (filename[skiplen + matchlen]) { - case '/': case '\0': - goto matched; - } - } - if (!fnmatch(m + skiplen, filename + skiplen, 0)) - goto matched; - if (ps_matched) - ps_matched++; - continue; - matched: - if (ps_matched) - *ps_matched = 1; - return 1; - } - return 0; -} - static void show_dir_entry(const char *tag, struct dir_entry *ent) { int len = prefix_len; @@ -80,7 +44,7 @@ static void show_dir_entry(const char *tag, struct dir_entry *ent) if (len >= ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); - if (pathspec && !pathspec_match(pathspec, ps_matched, ent->name, len)) + if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched)) return; fputs(tag, stdout); @@ -156,7 +120,7 @@ static void show_ce_entry(const char *tag, struct cache_entry *ce) if (len >= ce_namelen(ce)) die("git ls-files: internal error - cache entry not superset of prefix"); - if (pathspec && !pathspec_match(pathspec, ps_matched, ce->name, len)) + if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched)) return; if (tag && *tag && show_valid_bit && diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index f7c8c08b3..2789ccdf7 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -29,6 +29,9 @@ static struct strbuf **p_hdr_data, **s_hdr_data; #define MAX_HDR_PARSED 10 #define MAX_BOUNDARIES 5 +static void cleanup_space(struct strbuf *sb); + + static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) { struct strbuf *src = name; @@ -109,11 +112,19 @@ static void handle_from(const struct strbuf *from) strbuf_add(&email, at, el); strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); - /* The remainder is name. It could be "John Doe <john.doe@xz>" - * or "john.doe@xz (John Doe)", but we have removed the - * email part, so trim from both ends, possibly removing - * the () pair at the end. + /* The remainder is name. It could be + * + * - "John Doe <john.doe@xz>" (a), or + * - "john.doe@xz (John Doe)" (b), or + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) + * + * but we have removed the email part, so + * + * - remove extra spaces which could stay after email (case 'c'), and + * - trim from both ends, possibly removing the () pair at the end + * (cases 'a' and 'b'). */ + cleanup_space(&f); strbuf_trim(&f); if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { strbuf_remove(&f, 0, 1); @@ -430,13 +441,6 @@ static struct strbuf *decode_b_segment(const struct strbuf *b_seg) c -= 'a' - 26; else if ('0' <= c && c <= '9') c -= '0' - 52; - else if (c == '=') { - /* padding is almost like (c == 0), except we do - * not output NUL resulting only from it; - * for now we just trust the data. - */ - c = 0; - } else continue; /* garbage */ switch (pos++) { @@ -514,7 +518,25 @@ static int decode_header_bq(struct strbuf *it) rfc2047 = 1; if (in != ep) { - strbuf_add(&outbuf, in, ep - in); + /* + * We are about to process an encoded-word + * that begins at ep, but there is something + * before the encoded word. + */ + char *scan; + for (scan = in; scan < ep; scan++) + if (!isspace(*scan)) + break; + + if (scan != ep || in == it->buf) { + /* + * We should not lose that "something", + * unless we have just processed an + * encoded-word, and there is only LWS + * before the one we are about to process. + */ + strbuf_add(&outbuf, in, ep - in); + } in = ep; } /* E.g. @@ -860,6 +882,7 @@ static void handle_info(void) } output_header_lines(fout, "Subject", hdr); } else if (!memcmp(header[i], "From", 4)) { + cleanup_space(hdr); handle_from(hdr); fprintf(fout, "Author: %s\n", name.buf); fprintf(fout, "Email: %s\n", email.buf); diff --git a/builtin-merge.c b/builtin-merge.c index e4555b019..885fad9bb 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -36,8 +36,8 @@ struct strategy { }; static const char * const builtin_merge_usage[] = { - "git-merge [options] <remote>...", - "git-merge [options] <msg> HEAD <remote>", + "git merge [options] <remote>...", + "git merge [options] <msg> HEAD <remote>", NULL }; diff --git a/builtin-mv.c b/builtin-mv.c index bce995929..01270fefd 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -162,7 +162,9 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } argc += last - first; } - } else if (lstat(dst, &st) == 0) { + } else if (cache_name_pos(src, length) < 0) + bad = "not under version control"; + else if (lstat(dst, &st) == 0) { bad = "destination exists"; if (force) { /* @@ -177,9 +179,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } else bad = "Cannot overwrite"; } - } else if (cache_name_pos(src, length) < 0) - bad = "not under version control"; - else if (string_list_has_string(&src_for_dst, dst)) + } else if (string_list_has_string(&src_for_dst, dst)) bad = "multiple sources for the same target"; else string_list_insert(dst, &src_for_dst); diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 6564a97ce..596dfe922 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -9,7 +9,7 @@ #include "remote.h" #include "transport.h" -static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; +static const char receive_pack_usage[] = "git receive-pack <git-dir>"; enum deny_action { DENY_IGNORE, diff --git a/builtin-send-pack.c b/builtin-send-pack.c index a9fdbf9d4..d65d01969 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -15,6 +15,20 @@ static struct send_pack_args args = { /* .receivepack = */ "git-receive-pack", }; +static int feed_object(const unsigned char *sha1, int fd, int negative) +{ + char buf[42]; + + if (negative && !has_sha1_file(sha1)) + return 1; + + memcpy(buf + negative, sha1_to_hex(sha1), 40); + if (negative) + buf[0] = '^'; + buf[40 + negative] = '\n'; + return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs"); +} + /* * Make a pack stream and spit it out into file descriptor fd */ @@ -35,7 +49,6 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext }; struct child_process po; int i; - char buf[42]; if (args.use_thin_pack) argv[4] = "--thin"; @@ -51,31 +64,17 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext * We feed the pack-objects we just spawned with revision * parameters by writing to the pipe. */ - for (i = 0; i < extra->nr; i++) { - memcpy(buf + 1, sha1_to_hex(&extra->array[i][0]), 40); - buf[0] = '^'; - buf[41] = '\n'; - if (!write_or_whine(po.in, buf, 42, "send-pack: send refs")) + for (i = 0; i < extra->nr; i++) + if (!feed_object(extra->array[i], po.in, 1)) break; - } while (refs) { if (!is_null_sha1(refs->old_sha1) && - has_sha1_file(refs->old_sha1)) { - memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40); - buf[0] = '^'; - buf[41] = '\n'; - if (!write_or_whine(po.in, buf, 42, - "send-pack: send refs")) - break; - } - if (!is_null_sha1(refs->new_sha1)) { - memcpy(buf, sha1_to_hex(refs->new_sha1), 40); - buf[40] = '\n'; - if (!write_or_whine(po.in, buf, 41, - "send-pack: send refs")) - break; - } + !feed_object(refs->old_sha1, po.in, 1)) + break; + if (!is_null_sha1(refs->new_sha1) && + !feed_object(refs->new_sha1, po.in, 0)) + break; refs = refs->next; } diff --git a/builtin-tag.c b/builtin-tag.c index a39849989..01e73747d 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -26,6 +26,7 @@ static char signingkey[1000]; struct tag_filter { const char *pattern; int lines; + struct commit_list *with_commit; }; #define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----" @@ -42,6 +43,16 @@ static int show_reference(const char *refname, const unsigned char *sha1, char *buf, *sp, *eol; size_t len; + if (filter->with_commit) { + struct commit *commit; + + commit = lookup_commit_reference_gently(sha1, 1); + if (!commit) + return 0; + if (!is_descendant_of(commit, filter->with_commit)) + return 0; + } + if (!filter->lines) { printf("%s\n", refname); return 0; @@ -79,7 +90,8 @@ static int show_reference(const char *refname, const unsigned char *sha1, return 0; } -static int list_tags(const char *pattern, int lines) +static int list_tags(const char *pattern, int lines, + struct commit_list *with_commit) { struct tag_filter filter; @@ -88,6 +100,7 @@ static int list_tags(const char *pattern, int lines) filter.pattern = pattern; filter.lines = lines; + filter.with_commit = with_commit; for_each_tag_ref(show_reference, (void *) &filter); @@ -360,6 +373,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) list = 0, delete = 0, verify = 0; const char *msgfile = NULL, *keyid = NULL; struct msg_arg msg = { 0, STRBUF_INIT }; + struct commit_list *with_commit = NULL; struct option options[] = { OPT_BOOLEAN('l', NULL, &list, "list tag names"), { OPTION_INTEGER, 'n', NULL, &lines, NULL, @@ -378,6 +392,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix) 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_GROUP("Tag listing options"), + { + OPTION_CALLBACK, 0, "contains", &with_commit, "commit", + "print only tags that contain the commit", + PARSE_OPT_LASTARG_DEFAULT, + parse_opt_with_commit, (intptr_t)"HEAD", + }, OPT_END() }; @@ -402,9 +424,12 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (list + delete + verify > 1) usage_with_options(git_tag_usage, options); if (list) - return list_tags(argv[0], lines == -1 ? 0 : lines); + return list_tags(argv[0], lines == -1 ? 0 : lines, + with_commit); if (lines != -1) die("-n option is only allowed with -l."); + if (with_commit) + die("--contains option is only allowed with -l."); if (delete) return for_each_tag_name(argv, delete_tag); if (verify) diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c index 25a29f11a..0ee0a9af6 100644 --- a/builtin-verify-pack.c +++ b/builtin-verify-pack.c @@ -107,7 +107,7 @@ static int verify_one_pack(const char *path, int verbose) return err; } -static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>..."; +static const char verify_pack_usage[] = "git verify-pack [-v] <pack>..."; int cmd_verify_pack(int argc, const char **argv, const char *prefix) { @@ -266,6 +266,8 @@ int create_bundle(struct bundle_header *header, const char *path, return error("unrecognized argument: %s'", argv[i]); } + object_array_remove_duplicates(&revs.pending); + for (i = 0; i < revs.pending.nr; i++) { struct object_array_entry *e = revs.pending.objects + i; unsigned char sha1[20]; @@ -667,6 +667,7 @@ extern int read_ref(const char *filename, unsigned char *sha1); extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); +extern int interpret_nth_last_branch(const char *str, struct strbuf *); extern int refname_match(const char *abbrev_name, const char *full_name, const char **rules); extern const char *ref_rev_parse_rules[]; @@ -721,6 +722,10 @@ struct checkout { extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath); extern int has_symlink_leading_path(int len, const char *name); +extern int has_symlink_or_noent_leading_path(int len, const char *name); +extern int has_dirs_only_path(int len, const char *name, int prefix_len); +extern void invalidate_lstat_cache(int len, const char *name); +extern void clear_lstat_cache(void); extern struct alternate_object_database { struct alternate_object_database *next; @@ -937,7 +942,6 @@ extern int ws_fix_copy(char *, const char *, int, unsigned, int *); extern int ws_blank_line(const char *line, int len, unsigned ws_rule); /* ls-files */ -int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen); int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset); void overlay_tree_on_cache(const char *tree_name, const char *prefix); @@ -202,3 +202,31 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...) va_end(args); return r; } + +/* + * This function splits the buffer by newlines and colors the lines individually. + * + * Returns 0 on success. + */ +int color_fwrite_lines(FILE *fp, const char *color, + size_t count, const char *buf) +{ + if (!*color) + return fwrite(buf, count, 1, fp) != 1; + while (count) { + char *p = memchr(buf, '\n', count); + if (p != buf && (fputs(color, fp) < 0 || + fwrite(buf, p ? p - buf : count, 1, fp) != 1 || + fputs(COLOR_RESET, fp) < 0)) + return -1; + if (!p) + return 0; + if (fputc('\n', fp) < 0) + return -1; + count -= p + 1 - buf; + buf = p + 1; + } + return 0; +} + + @@ -20,5 +20,6 @@ void color_parse(const char *value, const char *var, char *dst); void color_parse_mem(const char *value, int len, const char *var, char *dst); int color_fprintf(FILE *fp, const char *color, const char *fmt, ...); int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...); +int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf); #endif /* COLOR_H */ @@ -705,6 +705,21 @@ struct commit_list *get_merge_bases(struct commit *one, struct commit *two, return get_merge_bases_many(one, 1, &two, cleanup); } +int is_descendant_of(struct commit *commit, struct commit_list *with_commit) +{ + if (!with_commit) + return 1; + while (with_commit) { + struct commit *other; + + other = with_commit->item; + with_commit = with_commit->next; + if (in_merge_bases(other, &commit, 1)) + return 1; + } + return 0; +} + int in_merge_bases(struct commit *commit, struct commit **reference, int num) { struct commit_list *bases, *b; @@ -133,6 +133,7 @@ extern int is_repository_shallow(void); extern struct commit_list *get_shallow_commits(struct object_array *heads, int depth, int shallow_flag, int not_shallow_flag); +int is_descendant_of(struct commit *, struct commit_list *); int in_merge_bases(struct commit *, struct commit **, int); extern int interactive_add(int argc, const char **argv, const char *prefix); diff --git a/compat/mingw.h b/compat/mingw.h index 4f275cb8e..a25589880 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -21,12 +21,12 @@ typedef int pid_t; #define WEXITSTATUS(x) ((x) & 0xff) #define WIFSIGNALED(x) ((unsigned)(x) > 259) -#define SIGKILL 0 -#define SIGCHLD 0 -#define SIGPIPE 0 -#define SIGHUP 0 -#define SIGQUIT 0 -#define SIGALRM 100 +#define SIGHUP 1 +#define SIGQUIT 3 +#define SIGKILL 9 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGCHLD 17 #define F_GETFD 1 #define F_SETFD 2 diff --git a/config.mak.in b/config.mak.in index 55b25c3d2..7cce0c12d 100644 --- a/config.mak.in +++ b/config.mak.in @@ -13,9 +13,9 @@ TCLTK_PATH = @TCLTK_PATH@ prefix = @prefix@ exec_prefix = @exec_prefix@ bindir = @bindir@ -gitexecdir = @libexecdir@/git-core/ +gitexecdir = @libexecdir@/git-core datarootdir = @datarootdir@ -template_dir = @datadir@/git-core/templates/ +template_dir = @datadir@/git-core/templates mandir=@mandir@ diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 703f4c2e9..307bf5d4f 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -34,6 +34,12 @@ # are currently in a git repository. The %s token will be # the name of the current branch. # +# In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty +# value, unstaged (*) and staged (+) changes will be shown next +# to the branch name. You can configure this per-repository +# with the bash.showDirtyState variable, which defaults to true +# once GIT_PS1_SHOWDIRTYSTATE is enabled. +# # To submit patches: # # *) Read Documentation/SubmittingPatches @@ -116,10 +122,26 @@ __git_ps1 () fi fi + local w + local i + + if test -n "$GIT_PS1_SHOWDIRTYSTATE"; then + if test "$(git config --bool bash.showDirtyState)" != "false"; then + git diff --no-ext-diff --ignore-submodules \ + --quiet --exit-code || w="*" + if git rev-parse --quiet --verify HEAD >/dev/null; then + git diff-index --cached --quiet \ + --ignore-submodules HEAD -- || i="+" + else + i="#" + fi + fi + fi + if [ -n "${1-}" ]; then - printf "$1" "${b##refs/heads/}$r" + printf "$1" "${b##refs/heads/}$w$i$r" else - printf " (%s)" "${b##refs/heads/}$r" + printf " (%s)" "${b##refs/heads/}$w$i$r" fi fi } @@ -783,6 +805,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary --no-ext-diff --no-prefix --src-prefix= --dst-prefix= --inter-hunk-context= + --patience --raw " diff --git a/contrib/difftool/git-difftool-helper b/contrib/difftool/git-difftool-helper index 0c48506ee..db3af6a83 100755 --- a/contrib/difftool/git-difftool-helper +++ b/contrib/difftool/git-difftool-helper @@ -1,7 +1,7 @@ #!/bin/sh # git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher. -# It supports kdiff3, tkdiff, xxdiff, meld, opendiff, emerge, ecmerge, -# vimdiff, gvimdiff, and custom user-configurable tools. +# It supports kdiff3, kompare, tkdiff, xxdiff, meld, opendiff, +# emerge, ecmerge, vimdiff, gvimdiff, and custom user-configurable tools. # This script is typically launched by using the 'git difftool' # convenience command. # @@ -73,6 +73,10 @@ launch_merge_tool () { > /dev/null 2>&1 ;; + kompare) + "$merge_tool_path" "$LOCAL" "$REMOTE" + ;; + tkdiff) "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE" ;; @@ -134,7 +138,7 @@ valid_custom_tool() { # Built-in merge tools are always valid. valid_tool() { case "$1" in - kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) + kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) ;; # happy *) if ! valid_custom_tool "$1" @@ -177,31 +181,24 @@ fi # Try to guess an appropriate merge tool if no tool has been set. if test -z "$merge_tool"; then - # We have a $DISPLAY so try some common UNIX merge tools if test -n "$DISPLAY"; then - merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff" - # If gnome then prefer meld - if test -n "$GNOME_DESKTOP_SESSION_ID"; then - merge_tool_candidates="meld $merge_tool_candidates" - fi - # If KDE then prefer kdiff3 - if test "$KDE_FULL_SESSION" = "true"; then - merge_tool_candidates="kdiff3 $merge_tool_candidates" + # If gnome then prefer meld, otherwise, prefer kdiff3 or kompare + if test -n "$GNOME_DESKTOP_SESSION_ID" ; then + merge_tool_candidates="meld kdiff3 kompare tkdiff xxdiff gvimdiff" + else + merge_tool_candidates="kdiff3 kompare tkdiff xxdiff meld gvimdiff" fi fi - - # $EDITOR is emacs so add emerge as a candidate if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then - merge_tool_candidates="$merge_tool_candidates emerge" + # $EDITOR is emacs so add emerge as a candidate + merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff" + elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then + # $EDITOR is vim so add vimdiff as a candidate + merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge" + else + merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" fi - - # $EDITOR is vim so add vimdiff as a candidate - if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then - merge_tool_candidates="$merge_tool_candidates vimdiff" - fi - - merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" echo "merge tool candidates: $merge_tool_candidates" # Loop over each candidate and stop when a valid merge tool is found. diff --git a/contrib/difftool/git-difftool.txt b/contrib/difftool/git-difftool.txt index ca3dbd246..6e2610cda 100644 --- a/contrib/difftool/git-difftool.txt +++ b/contrib/difftool/git-difftool.txt @@ -28,7 +28,8 @@ OPTIONS --tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: - kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff + kdiff3, kompare, tkdiff, meld, xxdiff, emerge, + vimdiff, gvimdiff, ecmerge, and opendiff + If a merge resolution program is not specified, 'git-difftool' will use the configuration variable `merge.tool`. If the diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh new file mode 100755 index 000000000..c364dda69 --- /dev/null +++ b/contrib/git-resurrect.sh @@ -0,0 +1,180 @@ +#!/bin/sh + +USAGE="[-a] [-r] [-m] [-t] [-n] [-b <newname>] <name>" +LONG_USAGE="git-resurrect attempts to find traces of a branch tip +called <name>, and tries to resurrect it. Currently, the reflog is +searched for checkout messages, and with -r also merge messages. With +-m and -t, the history of all refs is scanned for Merge <name> into +other/Merge <other> into <name> (respectively) commit subjects, which +is rather slow but allows you to resurrect other people's topic +branches." + +OPTIONS_SPEC="\ +git resurrect $USAGE +-- +b,branch= save branch as <newname> instead of <name> +a,all same as -l -r -m -t +k,keep-going full rev-list scan (instead of first match) +l,reflog scan reflog for checkouts (enabled by default) +r,reflog-merges scan for merges recorded in reflog +m,merges scan for merges into other branches (slow) +t,merge-targets scan for merges of other branches into <name> +n,dry-run don't recreate the branch" + +. git-sh-setup + +search_reflog () { + sed -ne 's~^\([^ ]*\) .*\tcheckout: moving from '"$1"' .*~\1~p' \ + < "$GIT_DIR"/logs/HEAD +} + +search_reflog_merges () { + git rev-parse $( + sed -ne 's~^[^ ]* \([^ ]*\) .*\tmerge '"$1"':.*~\1^2~p' \ + < "$GIT_DIR"/logs/HEAD + ) +} + +_x40="[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]" +_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" + +search_merges () { + git rev-list --all --grep="Merge branch '$1'" \ + --pretty=tformat:"%P %s" | + sed -ne "/^$_x40 \($_x40\) Merge .*/ {s//\1/p;$early_exit}" +} + +search_merge_targets () { + git rev-list --all --grep="Merge branch '[^']*' into $branch\$" \ + --pretty=tformat:"%H %s" --all | + sed -ne "/^\($_x40\) Merge .*/ {s//\1/p;$early_exit} " +} + +dry_run= +early_exit=q +scan_reflog=t +scan_reflog_merges= +scan_merges= +scan_merge_targets= +new_name= + +while test "$#" != 0; do + case "$1" in + -b|--branch) + shift + new_name="$1" + ;; + -n|--dry-run) + dry_run=t + ;; + --no-dry-run) + dry_run= + ;; + -k|--keep-going) + early_exit= + ;; + --no-keep-going) + early_exit=q + ;; + -m|--merges) + scan_merges=t + ;; + --no-merges) + scan_merges= + ;; + -l|--reflog) + scan_reflog=t + ;; + --no-reflog) + scan_reflog= + ;; + -r|--reflog_merges) + scan_reflog_merges=t + ;; + --no-reflog_merges) + scan_reflog_merges= + ;; + -t|--merge-targets) + scan_merge_targets=t + ;; + --no-merge-targets) + scan_merge_targets= + ;; + -a|--all) + scan_reflog=t + scan_reflog_merges=t + scan_merges=t + scan_merge_targets=t + ;; + --) + shift + break + ;; + *) + usage + ;; + esac + shift +done + +test "$#" = 1 || usage + +all_strategies="$scan_reflog$scan_reflog_merges$scan_merges$scan_merge_targets" +if test -z "$all_strategies"; then + die "must enable at least one of -lrmt" +fi + +branch="$1" +test -z "$new_name" && new_name="$branch" + +if test ! -z "$scan_reflog"; then + if test -r "$GIT_DIR"/logs/HEAD; then + candidates="$(search_reflog $branch)" + else + die 'reflog scanning requested, but' \ + '$GIT_DIR/logs/HEAD not readable' + fi +fi +if test ! -z "$scan_reflog_merges"; then + if test -r "$GIT_DIR"/logs/HEAD; then + candidates="$candidates $(search_reflog_merges $branch)" + else + die 'reflog scanning requested, but' \ + '$GIT_DIR/logs/HEAD not readable' + fi +fi +if test ! -z "$scan_merges"; then + candidates="$candidates $(search_merges $branch)" +fi +if test ! -z "$scan_merge_targets"; then + candidates="$candidates $(search_merge_targets $branch)" +fi + +candidates="$(git rev-parse $candidates | sort -u)" + +if test -z "$candidates"; then + hint= + test "z$all_strategies" != "ztttt" \ + && hint=" (maybe try again with -a)" + die "no candidates for $branch found$hint" +fi + +echo "** Candidates for $branch **" +for cmt in $candidates; do + git --no-pager log --pretty=tformat:"%ct:%h [%cr] %s" --abbrev-commit -1 $cmt +done \ +| sort -n | cut -d: -f2- + +newest="$(git rev-list -1 $candidates)" +if test ! -z "$dry_run"; then + printf "** Most recent: " + git --no-pager log -1 --pretty=tformat:"%h %s" $newest +elif ! git rev-parse --verify --quiet $new_name >/dev/null; then + printf "** Restoring $new_name to " + git --no-pager log -1 --pretty=tformat:"%h %s" $newest + git branch $new_name $newest +else + printf "Most recent: " + git --no-pager log -1 --pretty=tformat:"%h %s" $newest + echo "** $new_name already exists, doing nothing" +fi @@ -937,6 +937,8 @@ int main(int argc, char **argv) gid_t gid = 0; int i; + git_extract_argv0_path(argv[0]); + for (i = 1; i < argc; i++) { char *arg = argv[i]; diff --git a/diff-no-index.c b/diff-no-index.c index 60ed17470..0dbd9dad8 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -40,7 +40,7 @@ static int get_mode(const char *path, int *mode) *mode = 0; else if (!strcmp(path, "-")) *mode = create_ce_mode(0666); - else if (stat(path, &st)) + else if (lstat(path, &st)) return error("Could not access '%s'", path); else *mode = st.st_mode; @@ -12,6 +12,7 @@ #include "run-command.h" #include "utf8.h" #include "userdiff.h" +#include "sigchain.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -23,6 +24,7 @@ static int diff_detect_rename_default; static int diff_rename_limit_default = 200; static int diff_suppress_blank_empty; int diff_use_color_default = -1; +static const char *diff_word_regex_cfg; static const char *external_diff_cmd_cfg; int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; @@ -92,6 +94,8 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "diff.external")) return git_config_string(&external_diff_cmd_cfg, var, value); + if (!strcmp(var, "diff.wordregex")) + return git_config_string(&diff_word_regex_cfg, var, value); return git_diff_basic_config(var, value, cb); } @@ -167,6 +171,33 @@ static struct diff_tempfile { char tmp_path[PATH_MAX]; } diff_temp[2]; +static struct diff_tempfile *claim_diff_tempfile(void) { + int i; + for (i = 0; i < ARRAY_SIZE(diff_temp); i++) + if (!diff_temp[i].name) + return diff_temp + i; + die("BUG: diff is failing to clean up its tempfiles"); +} + +static int remove_tempfile_installed; + +static void remove_tempfile(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(diff_temp); i++) + if (diff_temp[i].name == diff_temp[i].tmp_path) { + unlink(diff_temp[i].name); + diff_temp[i].name = NULL; + } +} + +static void remove_tempfile_on_signal(int signo) +{ + remove_tempfile(); + sigchain_pop(signo); + raise(signo); +} + static int count_lines(const char *data, int size) { int count, ch, completely_empty = 1, nl_just_seen = 0; @@ -321,82 +352,138 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) struct diff_words_buffer { mmfile_t text; long alloc; - long current; /* output pointer */ - int suppressed_newline; + struct diff_words_orig { + const char *begin, *end; + } *orig; + int orig_nr, orig_alloc; }; static void diff_words_append(char *line, unsigned long len, struct diff_words_buffer *buffer) { - if (buffer->text.size + len > buffer->alloc) { - buffer->alloc = (buffer->text.size + len) * 3 / 2; - buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc); - } + ALLOC_GROW(buffer->text.ptr, buffer->text.size + len, buffer->alloc); line++; len--; memcpy(buffer->text.ptr + buffer->text.size, line, len); buffer->text.size += len; + buffer->text.ptr[buffer->text.size] = '\0'; } struct diff_words_data { struct diff_words_buffer minus, plus; + const char *current_plus; FILE *file; + regex_t *word_regex; }; -static void print_word(FILE *file, struct diff_words_buffer *buffer, int len, int color, - int suppress_newline) +static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) { - const char *ptr; - int eol = 0; + struct diff_words_data *diff_words = priv; + int minus_first, minus_len, plus_first, plus_len; + const char *minus_begin, *minus_end, *plus_begin, *plus_end; - if (len == 0) + if (line[0] != '@' || parse_hunk_header(line, len, + &minus_first, &minus_len, &plus_first, &plus_len)) return; - ptr = buffer->text.ptr + buffer->current; - buffer->current += len; + /* POSIX requires that first be decremented by one if len == 0... */ + if (minus_len) { + minus_begin = diff_words->minus.orig[minus_first].begin; + minus_end = + diff_words->minus.orig[minus_first + minus_len - 1].end; + } else + minus_begin = minus_end = + diff_words->minus.orig[minus_first].end; - if (ptr[len - 1] == '\n') { - eol = 1; - len--; + if (plus_len) { + plus_begin = diff_words->plus.orig[plus_first].begin; + plus_end = diff_words->plus.orig[plus_first + plus_len - 1].end; + } else + plus_begin = plus_end = diff_words->plus.orig[plus_first].end; + + if (diff_words->current_plus != plus_begin) + fwrite(diff_words->current_plus, + plus_begin - diff_words->current_plus, 1, + diff_words->file); + if (minus_begin != minus_end) + color_fwrite_lines(diff_words->file, + diff_get_color(1, DIFF_FILE_OLD), + minus_end - minus_begin, minus_begin); + if (plus_begin != plus_end) + color_fwrite_lines(diff_words->file, + diff_get_color(1, DIFF_FILE_NEW), + plus_end - plus_begin, plus_begin); + + diff_words->current_plus = plus_end; +} + +/* This function starts looking at *begin, and returns 0 iff a word was found. */ +static int find_word_boundaries(mmfile_t *buffer, regex_t *word_regex, + int *begin, int *end) +{ + if (word_regex && *begin < buffer->size) { + regmatch_t match[1]; + if (!regexec(word_regex, buffer->ptr + *begin, 1, match, 0)) { + char *p = memchr(buffer->ptr + *begin + match[0].rm_so, + '\n', match[0].rm_eo - match[0].rm_so); + *end = p ? p - buffer->ptr : match[0].rm_eo + *begin; + *begin += match[0].rm_so; + return *begin >= *end; + } + return -1; } - fputs(diff_get_color(1, color), file); - fwrite(ptr, len, 1, file); - fputs(diff_get_color(1, DIFF_RESET), file); + /* find the next word */ + while (*begin < buffer->size && isspace(buffer->ptr[*begin])) + (*begin)++; + if (*begin >= buffer->size) + return -1; - if (eol) { - if (suppress_newline) - buffer->suppressed_newline = 1; - else - putc('\n', file); - } + /* find the end of the word */ + *end = *begin + 1; + while (*end < buffer->size && !isspace(buffer->ptr[*end])) + (*end)++; + + return 0; } -static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) +/* + * This function splits the words in buffer->text, stores the list with + * newline separator into out, and saves the offsets of the original words + * in buffer->orig. + */ +static void diff_words_fill(struct diff_words_buffer *buffer, mmfile_t *out, + regex_t *word_regex) { - struct diff_words_data *diff_words = priv; + int i, j; + long alloc = 0; - if (diff_words->minus.suppressed_newline) { - if (line[0] != '+') - putc('\n', diff_words->file); - diff_words->minus.suppressed_newline = 0; - } + out->size = 0; + out->ptr = NULL; - len--; - switch (line[0]) { - case '-': - print_word(diff_words->file, - &diff_words->minus, len, DIFF_FILE_OLD, 1); - break; - case '+': - print_word(diff_words->file, - &diff_words->plus, len, DIFF_FILE_NEW, 0); - break; - case ' ': - print_word(diff_words->file, - &diff_words->plus, len, DIFF_PLAIN, 0); - diff_words->minus.current += len; - break; + /* fake an empty "0th" word */ + ALLOC_GROW(buffer->orig, 1, buffer->orig_alloc); + buffer->orig[0].begin = buffer->orig[0].end = buffer->text.ptr; + buffer->orig_nr = 1; + + for (i = 0; i < buffer->text.size; i++) { + if (find_word_boundaries(&buffer->text, word_regex, &i, &j)) + return; + + /* store original boundaries */ + ALLOC_GROW(buffer->orig, buffer->orig_nr + 1, + buffer->orig_alloc); + buffer->orig[buffer->orig_nr].begin = buffer->text.ptr + i; + buffer->orig[buffer->orig_nr].end = buffer->text.ptr + j; + buffer->orig_nr++; + + /* store one word */ + ALLOC_GROW(out->ptr, out->size + j - i + 1, alloc); + memcpy(out->ptr + out->size, buffer->text.ptr + i, j - i); + out->ptr[out->size + j - i] = '\n'; + out->size += j - i + 1; + + i = j - 1; } } @@ -407,38 +494,36 @@ static void diff_words_show(struct diff_words_data *diff_words) xdemitconf_t xecfg; xdemitcb_t ecb; mmfile_t minus, plus; - int i; + + /* special case: only removal */ + if (!diff_words->plus.text.size) { + color_fwrite_lines(diff_words->file, + diff_get_color(1, DIFF_FILE_OLD), + diff_words->minus.text.size, diff_words->minus.text.ptr); + diff_words->minus.text.size = 0; + return; + } + + diff_words->current_plus = diff_words->plus.text.ptr; memset(&xpp, 0, sizeof(xpp)); memset(&xecfg, 0, sizeof(xecfg)); - minus.size = diff_words->minus.text.size; - minus.ptr = xmalloc(minus.size); - memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size); - for (i = 0; i < minus.size; i++) - if (isspace(minus.ptr[i])) - minus.ptr[i] = '\n'; - diff_words->minus.current = 0; - - plus.size = diff_words->plus.text.size; - plus.ptr = xmalloc(plus.size); - memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size); - for (i = 0; i < plus.size; i++) - if (isspace(plus.ptr[i])) - plus.ptr[i] = '\n'; - diff_words->plus.current = 0; - + diff_words_fill(&diff_words->minus, &minus, diff_words->word_regex); + diff_words_fill(&diff_words->plus, &plus, diff_words->word_regex); xpp.flags = XDF_NEED_MINIMAL; - xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc; + /* as only the hunk header will be parsed, we need a 0-context */ + xecfg.ctxlen = 0; xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words, &xpp, &xecfg, &ecb); free(minus.ptr); free(plus.ptr); + if (diff_words->current_plus != diff_words->plus.text.ptr + + diff_words->plus.text.size) + fwrite(diff_words->current_plus, + diff_words->plus.text.ptr + diff_words->plus.text.size + - diff_words->current_plus, 1, + diff_words->file); diff_words->minus.text.size = diff_words->plus.text.size = 0; - - if (diff_words->minus.suppressed_newline) { - putc('\n', diff_words->file); - diff_words->minus.suppressed_newline = 0; - } } typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len); @@ -462,7 +547,10 @@ static void free_diff_words_data(struct emit_callback *ecbdata) diff_words_show(ecbdata->diff_words); free (ecbdata->diff_words->minus.text.ptr); + free (ecbdata->diff_words->minus.orig); free (ecbdata->diff_words->plus.text.ptr); + free (ecbdata->diff_words->plus.orig); + free(ecbdata->diff_words->word_regex); free(ecbdata->diff_words); ecbdata->diff_words = NULL; } @@ -1325,6 +1413,12 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe return one->driver->funcname.pattern ? &one->driver->funcname : NULL; } +static const char *userdiff_word_regex(struct diff_filespec *one) +{ + diff_filespec_load_driver(one); + return one->driver->word_regex; +} + void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b) { if (!options->a_prefix) @@ -1485,6 +1579,21 @@ static void builtin_diff(const char *name_a, ecbdata.diff_words = xcalloc(1, sizeof(struct diff_words_data)); ecbdata.diff_words->file = o->file; + if (!o->word_regex) + o->word_regex = userdiff_word_regex(one); + if (!o->word_regex) + o->word_regex = userdiff_word_regex(two); + if (!o->word_regex) + o->word_regex = diff_word_regex_cfg; + if (o->word_regex) { + ecbdata.diff_words->word_regex = (regex_t *) + xmalloc(sizeof(regex_t)); + if (regcomp(ecbdata.diff_words->word_regex, + o->word_regex, + REG_EXTENDED | REG_NEWLINE)) + die ("Invalid regular expression: %s", + o->word_regex); + } } xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata, &xpp, &xecfg, &ecb); @@ -1859,10 +1968,11 @@ static void prep_temp_blob(struct diff_tempfile *temp, sprintf(temp->mode, "%06o", mode); } -static void prepare_temp_file(const char *name, - struct diff_tempfile *temp, - struct diff_filespec *one) +static struct diff_tempfile *prepare_temp_file(const char *name, + struct diff_filespec *one) { + struct diff_tempfile *temp = claim_diff_tempfile(); + if (!DIFF_FILE_VALID(one)) { not_a_valid_file: /* A '-' entry produces this for file-2, and @@ -1871,7 +1981,13 @@ static void prepare_temp_file(const char *name, temp->name = "/dev/null"; strcpy(temp->hex, "."); strcpy(temp->mode, "."); - return; + return temp; + } + + if (!remove_tempfile_installed) { + atexit(remove_tempfile); + sigchain_push_common(remove_tempfile_on_signal); + remove_tempfile_installed = 1; } if (!one->sha1_valid || @@ -1911,7 +2027,7 @@ static void prepare_temp_file(const char *name, */ sprintf(temp->mode, "%06o", one->mode); } - return; + return temp; } else { if (diff_populate_filespec(one, 0)) @@ -1919,24 +2035,7 @@ static void prepare_temp_file(const char *name, prep_temp_blob(temp, one->data, one->size, one->sha1, one->mode); } -} - -static void remove_tempfile(void) -{ - int i; - - for (i = 0; i < 2; i++) - if (diff_temp[i].name == diff_temp[i].tmp_path) { - unlink(diff_temp[i].name); - diff_temp[i].name = NULL; - } -} - -static void remove_tempfile_on_signal(int signo) -{ - remove_tempfile(); - signal(SIGINT, SIG_DFL); - raise(signo); + return temp; } /* An external diff command takes: @@ -1954,34 +2053,22 @@ static void run_external_diff(const char *pgm, int complete_rewrite) { const char *spawn_arg[10]; - struct diff_tempfile *temp = diff_temp; int retval; - static int atexit_asked = 0; - const char *othername; const char **arg = &spawn_arg[0]; - othername = (other? other : name); - if (one && two) { - prepare_temp_file(name, &temp[0], one); - prepare_temp_file(othername, &temp[1], two); - if (! atexit_asked && - (temp[0].name == temp[0].tmp_path || - temp[1].name == temp[1].tmp_path)) { - atexit_asked = 1; - atexit(remove_tempfile); - } - signal(SIGINT, remove_tempfile_on_signal); - } - if (one && two) { + struct diff_tempfile *temp_one, *temp_two; + const char *othername = (other ? other : name); + temp_one = prepare_temp_file(name, one); + temp_two = prepare_temp_file(othername, two); *arg++ = pgm; *arg++ = name; - *arg++ = temp[0].name; - *arg++ = temp[0].hex; - *arg++ = temp[0].mode; - *arg++ = temp[1].name; - *arg++ = temp[1].hex; - *arg++ = temp[1].mode; + *arg++ = temp_one->name; + *arg++ = temp_one->hex; + *arg++ = temp_one->mode; + *arg++ = temp_two->name; + *arg++ = temp_two->hex; + *arg++ = temp_two->mode; if (other) { *arg++ = other; *arg++ = xfrm_msg; @@ -2000,16 +2087,86 @@ static void run_external_diff(const char *pgm, } } +static int similarity_index(struct diff_filepair *p) +{ + return p->score * 100 / MAX_SCORE; +} + +static void fill_metainfo(struct strbuf *msg, + const char *name, + const char *other, + struct diff_filespec *one, + struct diff_filespec *two, + struct diff_options *o, + struct diff_filepair *p) +{ + strbuf_init(msg, PATH_MAX * 2 + 300); + switch (p->status) { + case DIFF_STATUS_COPIED: + strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); + strbuf_addstr(msg, "\ncopy from "); + quote_c_style(name, msg, NULL, 0); + strbuf_addstr(msg, "\ncopy to "); + quote_c_style(other, msg, NULL, 0); + strbuf_addch(msg, '\n'); + break; + case DIFF_STATUS_RENAMED: + strbuf_addf(msg, "similarity index %d%%", similarity_index(p)); + strbuf_addstr(msg, "\nrename from "); + quote_c_style(name, msg, NULL, 0); + strbuf_addstr(msg, "\nrename to "); + quote_c_style(other, msg, NULL, 0); + strbuf_addch(msg, '\n'); + break; + case DIFF_STATUS_MODIFIED: + if (p->score) { + strbuf_addf(msg, "dissimilarity index %d%%\n", + similarity_index(p)); + break; + } + /* fallthru */ + default: + /* nothing */ + ; + } + if (one && two && hashcmp(one->sha1, two->sha1)) { + int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; + + if (DIFF_OPT_TST(o, BINARY)) { + mmfile_t mf; + if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) || + (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) + abbrev = 40; + } + strbuf_addf(msg, "index %.*s..%.*s", + abbrev, sha1_to_hex(one->sha1), + abbrev, sha1_to_hex(two->sha1)); + if (one->mode == two->mode) + strbuf_addf(msg, " %06o", one->mode); + strbuf_addch(msg, '\n'); + } + if (msg->len) + strbuf_setlen(msg, msg->len - 1); +} + static void run_diff_cmd(const char *pgm, const char *name, const char *other, const char *attr_path, struct diff_filespec *one, struct diff_filespec *two, - const char *xfrm_msg, + struct strbuf *msg, struct diff_options *o, - int complete_rewrite) + struct diff_filepair *p) { + const char *xfrm_msg = NULL; + int complete_rewrite = (p->status == DIFF_STATUS_MODIFIED) && p->score; + + if (msg) { + fill_metainfo(msg, name, other, one, two, o, p); + xfrm_msg = msg->len ? msg->buf : NULL; + } + if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL)) pgm = NULL; else { @@ -2049,11 +2206,6 @@ static void diff_fill_sha1_info(struct diff_filespec *one) hashclr(one->sha1); } -static int similarity_index(struct diff_filepair *p) -{ - return p->score * 100 / MAX_SCORE; -} - static void strip_prefix(int prefix_length, const char **namep, const char **otherp) { /* Strip the prefix but do not molest /dev/null and absolute paths */ @@ -2067,13 +2219,11 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) { const char *pgm = external_diff(); struct strbuf msg; - char *xfrm_msg; struct diff_filespec *one = p->one; struct diff_filespec *two = p->two; const char *name; const char *other; const char *attr_path; - int complete_rewrite = 0; name = p->one->path; other = (strcmp(name, p->two->path) ? p->two->path : NULL); @@ -2083,83 +2233,34 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) if (DIFF_PAIR_UNMERGED(p)) { run_diff_cmd(pgm, name, NULL, attr_path, - NULL, NULL, NULL, o, 0); + NULL, NULL, NULL, o, p); return; } diff_fill_sha1_info(one); diff_fill_sha1_info(two); - strbuf_init(&msg, PATH_MAX * 2 + 300); - switch (p->status) { - case DIFF_STATUS_COPIED: - strbuf_addf(&msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(&msg, "\ncopy from "); - quote_c_style(name, &msg, NULL, 0); - strbuf_addstr(&msg, "\ncopy to "); - quote_c_style(other, &msg, NULL, 0); - strbuf_addch(&msg, '\n'); - break; - case DIFF_STATUS_RENAMED: - strbuf_addf(&msg, "similarity index %d%%", similarity_index(p)); - strbuf_addstr(&msg, "\nrename from "); - quote_c_style(name, &msg, NULL, 0); - strbuf_addstr(&msg, "\nrename to "); - quote_c_style(other, &msg, NULL, 0); - strbuf_addch(&msg, '\n'); - break; - case DIFF_STATUS_MODIFIED: - if (p->score) { - strbuf_addf(&msg, "dissimilarity index %d%%\n", - similarity_index(p)); - complete_rewrite = 1; - break; - } - /* fallthru */ - default: - /* nothing */ - ; - } - - if (hashcmp(one->sha1, two->sha1)) { - int abbrev = DIFF_OPT_TST(o, FULL_INDEX) ? 40 : DEFAULT_ABBREV; - - if (DIFF_OPT_TST(o, BINARY)) { - mmfile_t mf; - if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) || - (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two))) - abbrev = 40; - } - strbuf_addf(&msg, "index %.*s..%.*s", - abbrev, sha1_to_hex(one->sha1), - abbrev, sha1_to_hex(two->sha1)); - if (one->mode == two->mode) - strbuf_addf(&msg, " %06o", one->mode); - strbuf_addch(&msg, '\n'); - } - - if (msg.len) - strbuf_setlen(&msg, msg.len - 1); - xfrm_msg = msg.len ? msg.buf : NULL; - if (!pgm && DIFF_FILE_VALID(one) && DIFF_FILE_VALID(two) && (S_IFMT & one->mode) != (S_IFMT & two->mode)) { - /* a filepair that changes between file and symlink + /* + * a filepair that changes between file and symlink * needs to be split into deletion and creation. */ struct diff_filespec *null = alloc_filespec(two->path); run_diff_cmd(NULL, name, other, attr_path, - one, null, xfrm_msg, o, 0); + one, null, &msg, o, p); free(null); + strbuf_release(&msg); + null = alloc_filespec(one->path); run_diff_cmd(NULL, name, other, attr_path, - null, two, xfrm_msg, o, 0); + null, two, &msg, o, p); free(null); } else run_diff_cmd(pgm, name, other, attr_path, - one, two, xfrm_msg, o, complete_rewrite); + one, two, &msg, o, p); strbuf_release(&msg); } @@ -2474,6 +2575,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; else if (!strcmp(arg, "--ignore-space-at-eol")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL; + else if (!strcmp(arg, "--patience")) + options->xdl_opts |= XDF_PATIENCE_DIFF; /* flags options */ else if (!strcmp(arg, "--binary")) { @@ -2496,6 +2599,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_CLR(options, COLOR_DIFF); else if (!strcmp(arg, "--color-words")) options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS; + else if (!prefixcmp(arg, "--color-words=")) { + options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS; + options->word_regex = arg + 14; + } else if (!strcmp(arg, "--exit-code")) DIFF_OPT_SET(options, EXIT_WITH_STATUS); else if (!strcmp(arg, "--quiet")) @@ -3450,15 +3557,15 @@ void diff_unmerge(struct diff_options *options, static char *run_textconv(const char *pgm, struct diff_filespec *spec, size_t *outsize) { - struct diff_tempfile temp; + struct diff_tempfile *temp; const char *argv[3]; const char **arg = argv; struct child_process child; struct strbuf buf = STRBUF_INIT; - prepare_temp_file(spec->path, &temp, spec); + temp = prepare_temp_file(spec->path, spec); *arg++ = pgm; - *arg++ = temp.name; + *arg++ = temp->name; *arg = NULL; memset(&child, 0, sizeof(child)); @@ -3467,13 +3574,11 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec, if (start_command(&child) != 0 || strbuf_read(&buf, child.out, 0) < 0 || finish_command(&child) != 0) { - if (temp.name == temp.tmp_path) - unlink(temp.name); + remove_tempfile(); error("error running textconv command '%s'", pgm); return NULL; } - if (temp.name == temp.tmp_path) - unlink(temp.name); + remove_tempfile(); return strbuf_detach(&buf, outsize); } @@ -98,6 +98,7 @@ struct diff_options { int stat_width; int stat_name_width; + const char *word_regex; /* this is set by diffcore for DIFF_FORMAT_PATCH */ int found_changes; @@ -108,25 +108,28 @@ static int match_one(const char *match, const char *name, int namelen) * and a mark is left in seen[] array for pathspec element that * actually matched anything. */ -int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen) +int match_pathspec(const char **pathspec, const char *name, int namelen, + int prefix, char *seen) { - int retval; - const char *match; + int i, retval = 0; + + if (!pathspec) + return 1; name += prefix; namelen -= prefix; - for (retval = 0; (match = *pathspec++) != NULL; seen++) { + for (i = 0; pathspec[i] != NULL; i++) { int how; - if (retval && *seen == MATCHED_EXACTLY) + const char *match = pathspec[i] + prefix; + if (seen && seen[i] == MATCHED_EXACTLY) continue; - match += prefix; how = match_one(match, name, namelen); if (how) { if (retval < how) retval = how; - if (*seen < how) - *seen = how; + if (seen && seen[i] < how) + seen[i] = how; } } return retval; @@ -9,35 +9,25 @@ static void create_directories(const char *path, const struct checkout *state) const char *slash = path; while ((slash = strchr(slash+1, '/')) != NULL) { - struct stat st; - int stat_status; - len = slash - path; memcpy(buf, path, len); buf[len] = 0; - if (len <= state->base_dir_len) - /* - * checkout-index --prefix=<dir>; <dir> is - * allowed to be a symlink to an existing - * directory. - */ - stat_status = stat(buf, &st); - else - /* - * if there currently is a symlink, we would - * want to replace it with a real directory. - */ - stat_status = lstat(buf, &st); - - if (!stat_status && S_ISDIR(st.st_mode)) + /* + * For 'checkout-index --prefix=<dir>', <dir> is + * allowed to be a symlink to an existing directory, + * and we set 'state->base_dir_len' below, such that + * we test the path components of the prefix with the + * stat() function instead of the lstat() function. + */ + if (has_dirs_only_path(len, buf, state->base_dir_len)) continue; /* ok, it is already a directory. */ /* - * We know stat_status == 0 means something exists - * there and this mkdir would fail, but that is an - * error codepath; we do not care, as we unlink and - * mkdir again in such a case. + * If this mkdir() would fail, it could be that there + * is already a symlink or something else exists + * there, therefore we then try to unlink it and try + * one more time to create the directory. */ if (mkdir(buf, 0777)) { if (errno == EEXIST && state->force && diff --git a/exec_cmd.c b/exec_cmd.c index cdd35f919..f234066de 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -9,17 +9,78 @@ static const char *argv0_path; const char *system_path(const char *path) { - if (!is_absolute_path(path) && argv0_path) { - struct strbuf d = STRBUF_INIT; - strbuf_addf(&d, "%s/%s", argv0_path, path); - path = strbuf_detach(&d, NULL); +#ifdef RUNTIME_PREFIX + static const char *prefix; +#else + static const char *prefix = PREFIX; +#endif + struct strbuf d = STRBUF_INIT; + + if (is_absolute_path(path)) + return path; + +#ifdef RUNTIME_PREFIX + assert(argv0_path); + assert(is_absolute_path(argv0_path)); + + if (!prefix) { + const char *strip[] = { + GIT_EXEC_PATH, + BINDIR, + 0 + }; + const char **s; + + for (s = strip; *s; s++) { + const char *sargv = argv0_path + strlen(argv0_path); + const char *ss = *s + strlen(*s); + while (argv0_path < sargv && *s < ss + && (*sargv == *ss || + (is_dir_sep(*sargv) && is_dir_sep(*ss)))) { + sargv--; + ss--; + } + if (*s == ss) { + struct strbuf d = STRBUF_INIT; + /* We also skip the trailing directory separator. */ + assert(sargv - argv0_path - 1 >= 0); + strbuf_add(&d, argv0_path, sargv - argv0_path - 1); + prefix = strbuf_detach(&d, NULL); + break; + } + } } + + if (!prefix) { + prefix = PREFIX; + fprintf(stderr, "RUNTIME_PREFIX requested, " + "but prefix computation failed. " + "Using static fallback '%s'.\n", prefix); + } +#endif + + strbuf_addf(&d, "%s/%s", prefix, path); + path = strbuf_detach(&d, NULL); return path; } -void git_set_argv0_path(const char *path) +const char *git_extract_argv0_path(const char *argv0) { - argv0_path = path; + const char *slash; + + if (!argv0 || !*argv0) + return NULL; + slash = argv0 + strlen(argv0); + + while (argv0 <= slash && !is_dir_sep(*slash)) + slash--; + + if (slash >= argv0) { + argv0_path = xstrndup(argv0, slash - argv0); + return slash + 1; + } + + return argv0; } void git_set_argv_exec_path(const char *exec_path) @@ -61,9 +122,7 @@ void setup_path(void) const char *old_path = getenv("PATH"); struct strbuf new_path = STRBUF_INIT; - add_path(&new_path, argv_exec_path); - add_path(&new_path, getenv(EXEC_PATH_ENVIRONMENT)); - add_path(&new_path, system_path(GIT_EXEC_PATH)); + add_path(&new_path, git_exec_path()); add_path(&new_path, argv0_path); if (old_path) diff --git a/exec_cmd.h b/exec_cmd.h index 594f96138..e2b546b61 100644 --- a/exec_cmd.h +++ b/exec_cmd.h @@ -2,8 +2,8 @@ #define GIT_EXEC_CMD_H extern void git_set_argv_exec_path(const char *exec_path); -extern void git_set_argv0_path(const char *path); -extern const char* git_exec_path(void); +extern const char *git_extract_argv0_path(const char *path); +extern const char *git_exec_path(void); extern void setup_path(void); extern const char **prepare_git_cmd(const char **argv); extern int execv_git_cmd(const char **argv); /* NULL terminated */ diff --git a/fast-import.c b/fast-import.c index f0e08aca7..1935206be 100644 --- a/fast-import.c +++ b/fast-import.c @@ -150,6 +150,7 @@ Format of STDIN stream: #include "refs.h" #include "csum-file.h" #include "quote.h" +#include "exec_cmd.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -2406,6 +2407,8 @@ int main(int argc, const char **argv) { unsigned int i, show_stats = 1; + git_extract_argv0_path(argv[0]); + setup_git_directory(); git_config(git_pack_config, NULL); if (!pack_compression_seen && core_compression_seen) diff --git a/git-add--interactive.perl b/git-add--interactive.perl index ca60356d0..3bf0cda4e 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -801,6 +801,7 @@ n - do not stage this hunk a - stage this and all the remaining hunks in the file d - do not stage this hunk nor any of the remaining hunks in the file g - select a hunk to go to +/ - search for a hunk matching the given regex j - leave this hunk undecided, see next undecided hunk J - leave this hunk undecided, see next hunk k - leave this hunk undecided, see previous undecided hunk @@ -929,25 +930,25 @@ sub patch_update_file { for ($i = 0; $i < $ix; $i++) { if (!defined $hunk[$i]{USE}) { $prev = 1; - $other .= '/k'; + $other .= ',k'; last; } } if ($ix) { - $other .= '/K'; + $other .= ',K'; } for ($i = $ix + 1; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { $next = 1; - $other .= '/j'; + $other .= ',j'; last; } } if ($ix < $num - 1) { - $other .= '/J'; + $other .= ',J'; } if ($num > 1) { - $other .= '/g'; + $other .= ',g'; } for ($i = 0; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { @@ -958,13 +959,13 @@ sub patch_update_file { last if (!$undecided); if (hunk_splittable($hunk[$ix]{TEXT})) { - $other .= '/s'; + $other .= ',s'; } - $other .= '/e'; + $other .= ',e'; for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? "; + print colored $prompt_color, "Stage this hunk [y,n,a,d,/$other,?]? "; my $line = <STDIN>; if ($line) { if ($line =~ /^y/i) { @@ -993,6 +994,9 @@ sub patch_update_file { } print "go to which hunk$extra? "; $response = <STDIN>; + if (!defined $response) { + $response = ''; + } chomp $response; } if ($response !~ /^\s*\d+\s*$/) { @@ -1013,30 +1017,68 @@ sub patch_update_file { } next; } - elsif ($other =~ /K/ && $line =~ /^K/) { - $ix--; - next; - } - elsif ($other =~ /J/ && $line =~ /^J/) { - $ix++; + elsif ($line =~ m|^/(.*)|) { + my $search_string; + eval { + $search_string = qr{$1}m; + }; + if ($@) { + my ($err,$exp) = ($@, $1); + $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//; + print STDERR "Malformed search regexp $exp: $err\n"; + next; + } + my $iy = $ix; + while (1) { + my $text = join ("", @{$hunk[$iy]{TEXT}}); + last if ($text =~ $search_string); + $iy++; + $iy = 0 if ($iy >= $num); + if ($ix == $iy) { + print STDERR "No hunk matches the given pattern\n"; + last; + } + } + $ix = $iy; next; } - elsif ($other =~ /k/ && $line =~ /^k/) { - while (1) { + elsif ($line =~ /^K/) { + if ($other =~ /K/) { $ix--; - last if (!$ix || - !defined $hunk[$ix]{USE}); + } + else { + print STDERR "No previous hunk\n"; } next; } - elsif ($other =~ /j/ && $line =~ /^j/) { - while (1) { + elsif ($line =~ /^J/) { + if ($other =~ /J/) { $ix++; - last if ($ix >= $num || - !defined $hunk[$ix]{USE}); + } + else { + print STDERR "No next hunk\n"; + } + next; + } + elsif ($line =~ /^k/) { + if ($other =~ /k/) { + while (1) { + $ix--; + last if (!$ix || + !defined $hunk[$ix]{USE}); + } + } + else { + print STDERR "No previous hunk\n"; } next; } + elsif ($line =~ /^j/) { + if ($other !~ /j/) { + print STDERR "No next hunk\n"; + next; + } + } elsif ($other =~ /s/ && $line =~ /^s/) { my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) { @@ -8,9 +8,8 @@ OPTIONS_SPEC="\ git am [options] [<mbox>|<Maildir>...] git am [options] (--resolved | --skip | --abort) -- -d,dotest= (removed -- do not use) i,interactive run interactively -b,binary (historical option -- no-op) +b,binary* (historical option -- no-op) 3,3way allow fall back on 3way merging if needed s,signoff add a Signed-off-by line to the commit message u,utf8 recode into utf8 (default) @@ -24,7 +23,9 @@ resolvemsg= override error message when patch failure occurs r,resolved to be used after a patch failure skip skip the current patch abort restore the original branch and abort the patching operation. -rebasing (internal use for git-rebase)" +committer-date-is-author-date lie about committer date +ignore-date use current timestamp for author date +rebasing* (internal use for git-rebase)" . git-sh-setup prefix=$(git rev-parse --show-prefix) @@ -134,6 +135,8 @@ dotest="$GIT_DIR/rebase-apply" sign= utf8=t keep= skip= interactive= resolved= rebasing= abort= resolvemsg= resume= git_apply_opt= +committer_date_is_author_date= +ignore_date= while test $# != 0 do @@ -171,6 +174,10 @@ do git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;; --reject) git_apply_opt="$git_apply_opt $1" ;; + --committer-date-is-author-date) + committer_date_is_author_date=t ;; + --ignore-date) + ignore_date=t ;; --) shift; break ;; *) @@ -204,7 +211,7 @@ then # unreliable -- stdin could be /dev/null for example # and the caller did not intend to feed us a patch but # wanted to continue unattended. - tty -s + test -t 0 ;; *) false @@ -280,10 +287,7 @@ fi case "$resolved" in '') files=$(git diff-index --cached --name-only HEAD --) || exit - if [ "$files" ]; then - echo "Dirty index: cannot apply patches (dirty: $files)" >&2 - exit 1 - fi + test "$files" && die "Dirty index: cannot apply patches (dirty: $files)" esac if test "$(cat "$dotest/utf8")" = t @@ -524,7 +528,18 @@ do tree=$(git write-tree) && parent=$(git rev-parse --verify HEAD) && - commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") && + commit=$( + if test -n "$ignore_date" + then + GIT_AUTHOR_DATE= + fi + if test -n "$committer_date_is_author_date" + then + GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" + export GIT_COMMITTER_DATE + fi && + git commit-tree $tree -p $parent <"$dotest/final-commit" + ) && git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent || stop_here $this diff --git a/git-cvsserver.perl b/git-cvsserver.perl index fef7faf33..ab6cea3e5 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -76,6 +76,7 @@ my $methods = { 'history' => \&req_CATCHALL, 'watchers' => \&req_EMPTY, 'editors' => \&req_EMPTY, + 'noop' => \&req_EMPTY, 'annotate' => \&req_annotate, 'Global_option' => \&req_Globaloption, #'annotate' => \&req_CATCHALL, @@ -1413,14 +1414,14 @@ sub req_ci close $pipe || die "bad pipe: $! $?"; } + $updater->update(); + ### Then hooks/post-update $hook = $ENV{GIT_DIR}.'hooks/post-update'; if (-x $hook) { system($hook, "refs/heads/$state->{module}"); } - $updater->update(); - # foreach file specified on the command line ... foreach my $filename ( @committedfiles ) { diff --git a/git-mergetool.sh b/git-mergetool.sh index a9d2e29d4..87fa88af5 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -397,21 +397,19 @@ fi if test -z "$merge_tool" ; then if test -n "$DISPLAY"; then - merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff" if test -n "$GNOME_DESKTOP_SESSION_ID" ; then - merge_tool_candidates="meld $merge_tool_candidates" - fi - if test "$KDE_FULL_SESSION" = "true"; then - merge_tool_candidates="kdiff3 $merge_tool_candidates" + merge_tool_candidates="meld kdiff3 tkdiff xxdiff gvimdiff" + else + merge_tool_candidates="kdiff3 tkdiff xxdiff meld gvimdiff" fi fi if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then - merge_tool_candidates="$merge_tool_candidates emerge" - fi - if echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then - merge_tool_candidates="$merge_tool_candidates vimdiff" + merge_tool_candidates="$merge_tool_candidates emerge opendiff vimdiff" + elif echo "${VISUAL:-$EDITOR}" | grep 'vim' > /dev/null 2>&1; then + merge_tool_candidates="$merge_tool_candidates vimdiff opendiff emerge" + else + merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" fi - merge_tool_candidates="$merge_tool_candidates opendiff emerge vimdiff" echo "merge tool candidates: $merge_tool_candidates" for i in $merge_tool_candidates; do init_merge_tool_path $i diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 21ac20c30..3dc659dd5 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -373,17 +373,15 @@ do_next () { pick_one -n $sha1 || failed=t case "$(peek_next_command)" in squash|s) - EDIT_COMMIT= USE_OUTPUT=output MSG_OPT=-F - MSG_FILE="$MSG" + EDIT_OR_FILE="$MSG" cp "$MSG" "$SQUASH_MSG" ;; *) - EDIT_COMMIT=-e USE_OUTPUT= MSG_OPT= - MSG_FILE= + EDIT_OR_FILE=-e rm -f "$SQUASH_MSG" || exit cp "$MSG" "$GIT_DIR"/SQUASH_MSG rm -f "$GIT_DIR"/MERGE_MSG || exit @@ -397,7 +395,8 @@ do_next () { GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \ GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \ GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \ - $USE_OUTPUT git commit --no-verify $MSG_OPT "$MSG_FILE" $EDIT_COMMIT || failed=t + $USE_OUTPUT git commit --no-verify \ + $MSG_OPT "$EDIT_OR_FILE" || failed=t fi if test $failed = t then @@ -456,7 +455,7 @@ get_saved_options () { test -d "$REWRITTEN" && PRESERVE_MERGES=t test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)" test -f "$DOTEST"/verbose && VERBOSE=t - test ! -s "$DOTEST"/upstream && REBASE_ROOT=t + test -f "$DOTEST"/rebase-root && REBASE_ROOT=t } while test $# != 0 @@ -571,7 +570,8 @@ first and then run 'git rebase --continue' again." ;; --) shift - test ! -z "$REBASE_ROOT" -o $# -eq 1 -o $# -eq 2 || usage + test -z "$REBASE_ROOT" -a $# -ge 1 -a $# -le 2 || + test ! -z "$REBASE_ROOT" -a $# -le 1 || usage test -d "$DOTEST" && die "Interactive rebase already started" @@ -585,6 +585,7 @@ first and then run 'git rebase --continue' again." test -z "$ONTO" && ONTO=$UPSTREAM shift else + UPSTREAM= UPSTREAM_ARG=--root test -z "$ONTO" && die "You must specify --onto when using --root" @@ -611,7 +612,12 @@ first and then run 'git rebase --continue' again." echo "detached HEAD" > "$DOTEST"/head-name echo $HEAD > "$DOTEST"/head - echo $UPSTREAM > "$DOTEST"/upstream + case "$REBASE_ROOT" in + '') + rm -f "$DOTEST"/rebase-root ;; + *) + : >"$DOTEST"/rebase-root ;; + esac echo $ONTO > "$DOTEST"/onto test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy test t = "$VERBOSE" && : > "$DOTEST"/verbose diff --git a/git-svn.perl b/git-svn.perl index d4cb538b9..79888a05c 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -70,7 +70,8 @@ my ($_stdin, $_help, $_edit, $Git::SVN::_follow_parent = 1; my %remote_opts = ( 'username=s' => \$Git::SVN::Prompt::_username, 'config-dir=s' => \$Git::SVN::Ra::config_dir, - 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache ); + 'no-auth-cache' => \$Git::SVN::Prompt::_no_auth_cache, + 'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex ); my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, 'repack:i' => \$Git::SVN::_repack, @@ -3245,6 +3246,7 @@ use warnings; use Carp qw/croak/; use File::Temp qw/tempfile/; use IO::File qw//; +use vars qw/$_ignore_regex/; # file baton members: path, mode_a, mode_b, pool, fh, blob, base sub new { @@ -3297,6 +3299,15 @@ sub in_dot_git { $_[0] =~ m{(?:^|/)\.git(?:/|$)}; } +# return value: 0 -- don't ignore, 1 -- ignore +sub is_path_ignored { + my ($path) = @_; + return 1 if in_dot_git($path); + return 0 unless defined($_ignore_regex); + return 1 if $path =~ m!$_ignore_regex!o; + return 0; +} + sub set_path_strip { my ($self, $path) = @_; $self->{path_strip} = qr/^\Q$path\E(\/|$)/ if length $path; @@ -3322,7 +3333,7 @@ sub git_path { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - return undef if in_dot_git($path); + return undef if is_path_ignored($path); my $gpath = $self->git_path($path); return undef if ($gpath eq ''); @@ -3352,7 +3363,7 @@ sub open_file { my ($self, $path, $pb, $rev) = @_; my ($mode, $blob); - goto out if in_dot_git($path); + goto out if is_path_ignored($path); my $gpath = $self->git_path($path); ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath) @@ -3372,7 +3383,7 @@ sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; my $mode; - if (!in_dot_git($path)) { + if (!is_path_ignored($path)) { my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); delete $self->{empty}->{$dir}; $mode = '100644'; @@ -3383,7 +3394,7 @@ sub add_file { sub add_directory { my ($self, $path, $cp_path, $cp_rev) = @_; - goto out if in_dot_git($path); + goto out if is_path_ignored($path); my $gpath = $self->git_path($path); if ($gpath eq '') { my ($ls, $ctx) = command_output_pipe(qw/ls-tree @@ -3407,7 +3418,7 @@ out: sub change_dir_prop { my ($self, $db, $prop, $value) = @_; - return undef if in_dot_git($db->{path}); + return undef if is_path_ignored($db->{path}); $self->{dir_prop}->{$db->{path}} ||= {}; $self->{dir_prop}->{$db->{path}}->{$prop} = $value; undef; @@ -3415,7 +3426,7 @@ sub change_dir_prop { sub absent_directory { my ($self, $path, $pb) = @_; - return undef if in_dot_git($pb->{path}); + return undef if is_path_ignored($path); $self->{absent_dir}->{$pb->{path}} ||= []; push @{$self->{absent_dir}->{$pb->{path}}}, $path; undef; @@ -3423,7 +3434,7 @@ sub absent_directory { sub absent_file { my ($self, $path, $pb) = @_; - return undef if in_dot_git($pb->{path}); + return undef if is_path_ignored($path); $self->{absent_file}->{$pb->{path}} ||= []; push @{$self->{absent_file}->{$pb->{path}}}, $path; undef; @@ -3431,7 +3442,7 @@ sub absent_file { sub change_file_prop { my ($self, $fb, $prop, $value) = @_; - return undef if in_dot_git($fb->{path}); + return undef if is_path_ignored($fb->{path}); if ($prop eq 'svn:executable') { if ($fb->{mode_b} != 120000) { $fb->{mode_b} = defined $value ? 100755 : 100644; @@ -3447,7 +3458,7 @@ sub change_file_prop { sub apply_textdelta { my ($self, $fb, $exp) = @_; - return undef if (in_dot_git($fb->{path})); + return undef if is_path_ignored($fb->{path}); my $fh = $::_repository->temp_acquire('svn_delta'); # $fh gets auto-closed() by SVN::TxDelta::apply(), # (but $base does not,) so dup() it for reading in close_file @@ -3494,7 +3505,7 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; - return undef if (in_dot_git($fb->{path})); + return undef if is_path_ignored($fb->{path}); my $hash; my $path = $self->git_path($fb->{path}); @@ -4021,7 +4032,8 @@ my ($ra_invalid, $can_do_switch, %ignored_err, $RA); BEGIN { # enforce temporary pool usage for some simple functions no strict 'refs'; - for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) { + for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root + get_file/) { my $SUPER = "SUPER::$f"; *$f = sub { my $self = shift; @@ -2,6 +2,7 @@ #include "exec_cmd.h" #include "cache.h" #include "quote.h" +#include "run-command.h" const char git_usage_string[] = "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; @@ -219,7 +220,7 @@ struct cmd_struct { int option; }; -static int run_command(struct cmd_struct *p, int argc, const char **argv) +static int run_builtin(struct cmd_struct *p, int argc, const char **argv) { int status; struct stat st; @@ -384,7 +385,7 @@ static void handle_internal_command(int argc, const char **argv) struct cmd_struct *p = commands+i; if (strcmp(p->cmd, cmd)) continue; - exit(run_command(p, argc, argv)); + exit(run_builtin(p, argc, argv)); } } @@ -392,6 +393,7 @@ static void execv_dashed_external(const char **argv) { struct strbuf cmd = STRBUF_INIT; const char *tmp; + int status; strbuf_addf(&cmd, "git-%s", argv[0]); @@ -406,10 +408,17 @@ static void execv_dashed_external(const char **argv) trace_argv_printf(argv, "trace: exec:"); - /* execvp() can only ever return if it fails */ - execvp(cmd.buf, (char **)argv); - - trace_printf("trace: exec failed: %s\n", strerror(errno)); + /* + * 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 */ argv[0] = tmp; @@ -442,21 +451,11 @@ static int run_argv(int *argcp, const char ***argv) int main(int argc, const char **argv) { - const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help"; - char *slash = (char *)cmd + strlen(cmd); + const char *cmd; - /* - * Take the basename of argv[0] as the command - * name, and the dirname as the default exec_path - * if we don't have anything better. - */ - while (cmd <= slash && !is_dir_sep(*slash)) - slash--; - if (cmd <= slash) { - *slash++ = 0; - git_set_argv0_path(cmd); - cmd = slash; - } + cmd = git_extract_argv0_path(argv[0]); + if (!cmd) + cmd = "git-help"; /* * "git-xxxx" is the same as "git xxxx", but we obviously: diff --git a/git.spec.in b/git.spec.in index 069ace050..4be0834f0 100644 --- a/git.spec.in +++ b/git.spec.in @@ -97,7 +97,7 @@ BuildRequires: perl(Error) %description -n perl-Git Perl interface to Git -%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-core-%{version} +%define path_settings ETC_GITCONFIG=/etc/gitconfig prefix=%{_prefix} mandir=%{_mandir} htmldir=%{_docdir}/%{name}-%{version} %prep %setup -q @@ -190,6 +190,9 @@ rm -rf $RPM_BUILD_ROOT # No files for you! %changelog +* Mon Feb 04 2009 David J. Mellor <dmellor@whistlingcat.com> +- fixed broken git help -w after renaming the git-core package to git. + * Fri Sep 12 2008 Quy Tonthat <qtonthat@gmail.com> - move git-cvsserver to bindir. diff --git a/gitweb/README b/gitweb/README index 825162a0b..a9dc2e57d 100644 --- a/gitweb/README +++ b/gitweb/README @@ -162,14 +162,12 @@ not include variables usually directly set during build): $GITWEB_LIST during installation. If empty, $projectroot is used to scan for repositories. * $my_url, $my_uri - URL and absolute URL of gitweb script; you might need to set those - variables if you are using 'pathinfo' feature: see also below. + Full URL and absolute URL of gitweb script; + in earlier versions of gitweb you might have need to set those + variables, now there should be no need to do it. * $home_link Target of the home link on top of all pages (the first part of view - "breadcrumbs"). By default set to absolute URI of a page; you might - need to set it up to [base] gitweb URI if you use 'pathinfo' feature - (alternative format of the URLs, with project name embedded directly - in the path part of URL). + "breadcrumbs"). By default set to absolute URI of a page ($my_uri). * @stylesheets List of URIs of stylesheets (relative to base URI of a page). You might specify more than one stylesheet, for example use gitweb.css @@ -322,6 +320,82 @@ something like the following in your gitweb.conf (or gitweb_config.perl) file: $home_link = "/"; +PATH_INFO usage +----------------------- +If you enable PATH_INFO usage in gitweb by putting + + $feature{'pathinfo'}{'default'} = [1]; + +in your gitweb.conf, it is possible to set up your server so that it +consumes and produces URLs in the form + +http://git.example.com/project.git/shortlog/sometag + +by using a configuration such as the following, that assumes that +/var/www/gitweb is the DocumentRoot of your webserver, and that it +contains the gitweb.cgi script and complementary static files +(stylesheet, favicon): + +<VirtualHost *:80> + ServerAlias git.example.com + + DocumentRoot /var/www/gitweb + + <Directory /var/www/gitweb> + Options ExecCGI + AddHandler cgi-script cgi + + DirectoryIndex gitweb.cgi + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^.* /gitweb.cgi/$0 [L,PT] + </Directory> +</VirtualHost> + +The rewrite rule guarantees that existing static files will be properly +served, whereas any other URL will be passed to gitweb as PATH_INFO +parameter. + +Notice that in this case you don't need special settings for +@stylesheets, $my_uri and $home_link, but you lose "dumb client" access +to your project .git dirs. A possible workaround for the latter is the +following: in your project root dir (e.g. /pub/git) have the projects +named without a .git extension (e.g. /pub/git/project instead of +/pub/git/project.git) and configure Apache as follows: + +<VirtualHost *:80> + ServerAlias git.example.com + + DocumentRoot /var/www/gitweb + + AliasMatch ^(/.*?)(\.git)(/.*)? /pub/git$1$3 + <Directory /var/www/gitweb> + Options ExecCGI + AddHandler cgi-script cgi + + DirectoryIndex gitweb.cgi + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule ^.* /gitweb.cgi/$0 [L,PT] + </Directory> +</VirtualHost> + +The additional AliasMatch makes it so that + +http://git.example.com/project.git + +will give raw access to the project's git dir (so that the project can +be cloned), while + +http://git.example.com/project + +will provide human-friendly gitweb access. + + Originally written by: Kay Sievers <kay.sievers@vrfy.org> diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 931db4f7e..f27dbb6bf 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2901,9 +2901,14 @@ sub git_header_html { <meta name="robots" content="index, nofollow"/> <title>$title</title> EOF -# print out each stylesheet that exist + # the stylesheet, favicon etc urls won't work correctly with path_info + # unless we set the appropriate base URL + if ($ENV{'PATH_INFO'}) { + print '<base href="'.esc_url($my_url).'" />\n'; + } + # print out each stylesheet that exist, providing backwards capability + # for those people who defined $stylesheet in a config file if (defined $stylesheet) { -#provides backwards capability for those people who define style sheet in a config file print '<link rel="stylesheet" type="text/css" href="'.$stylesheet.'"/>'."\n"; } else { foreach my $stylesheet (@stylesheets) { @@ -6015,7 +6020,25 @@ sub git_feed { } if (defined($commitlist[0])) { %latest_commit = %{$commitlist[0]}; - %latest_date = parse_date($latest_commit{'author_epoch'}); + my $latest_epoch = $latest_commit{'committer_epoch'}; + %latest_date = parse_date($latest_epoch); + my $if_modified = $cgi->http('IF_MODIFIED_SINCE'); + if (defined $if_modified) { + my $since; + if (eval { require HTTP::Date; 1; }) { + $since = HTTP::Date::str2time($if_modified); + } elsif (eval { require Time::ParseDate; 1; }) { + $since = Time::ParseDate::parsedate($if_modified, GMT => 1); + } + if (defined $since && $latest_epoch <= $since) { + print $cgi->header( + -type => $content_type, + -charset => 'utf-8', + -last_modified => $latest_date{'rfc2822'}, + -status => '304 Not Modified'); + return; + } + } print $cgi->header( -type => $content_type, -charset => 'utf-8', @@ -6074,7 +6097,24 @@ XML print "<title>$title</title>\n" . "<link>$alt_url</link>\n" . "<description>$descr</description>\n" . - "<language>en</language>\n"; + "<language>en</language>\n" . + # project owner is responsible for 'editorial' content + "<managingEditor>$owner</managingEditor>\n"; + if (defined $logo || defined $favicon) { + # prefer the logo to the favicon, since RSS + # doesn't allow both + my $img = esc_url($logo || $favicon); + print "<image>\n" . + "<url>$img</url>\n" . + "<title>$title</title>\n" . + "<link>$alt_url</link>\n" . + "</image>\n"; + } + if (%latest_date) { + print "<pubDate>$latest_date{'rfc2822'}</pubDate>\n"; + print "<lastBuildDate>$latest_date{'rfc2822'}</lastBuildDate>\n"; + } + print "<generator>gitweb v.$version/$git_version</generator>\n"; } elsif ($format eq 'atom') { print <<XML; <feed xmlns="http://www.w3.org/2005/Atom"> @@ -6101,6 +6141,7 @@ XML } else { print "<updated>$latest_date{'iso-8601'}</updated>\n"; } + print "<generator version='$version/$git_version'>gitweb</generator>\n"; } # contents diff --git a/hash-object.c b/hash-object.c index 846e91a23..37e66779a 100644 --- a/hash-object.c +++ b/hash-object.c @@ -8,6 +8,7 @@ #include "blob.h" #include "quote.h" #include "parse-options.h" +#include "exec_cmd.h" static void hash_fd(int fd, const char *type, int write_object, const char *path) { @@ -81,6 +82,8 @@ int main(int argc, const char **argv) type = blob_type; + git_extract_argv0_path(argv[0]); + git_config(git_default_config, NULL); argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0); diff --git a/http-push.c b/http-push.c index cb5bf95a7..18d81acd1 100644 --- a/http-push.c +++ b/http-push.c @@ -10,6 +10,7 @@ #include "exec_cmd.h" #include "remote.h" #include "list-objects.h" +#include "sigchain.h" #include <expat.h> @@ -177,6 +178,38 @@ struct remote_ls_ctx struct remote_ls_ctx *parent; }; +/* get_dav_token_headers options */ +enum dav_header_flag { + DAV_HEADER_IF = (1u << 0), + DAV_HEADER_LOCK = (1u << 1), + DAV_HEADER_TIMEOUT = (1u << 2) +}; + +static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options) +{ + struct strbuf buf = STRBUF_INIT; + struct curl_slist *dav_headers = NULL; + + if (options & DAV_HEADER_IF) { + strbuf_addf(&buf, "If: (<%s>)", lock->token); + dav_headers = curl_slist_append(dav_headers, buf.buf); + strbuf_reset(&buf); + } + if (options & DAV_HEADER_LOCK) { + strbuf_addf(&buf, "Lock-Token: <%s>", lock->token); + dav_headers = curl_slist_append(dav_headers, buf.buf); + strbuf_reset(&buf); + } + if (options & DAV_HEADER_TIMEOUT) { + strbuf_addf(&buf, "Timeout: Second-%ld", lock->timeout); + dav_headers = curl_slist_append(dav_headers, buf.buf); + strbuf_reset(&buf); + } + strbuf_release(&buf); + + return dav_headers; +} + static void finish_request(struct transfer_request *request); static void release_request(struct transfer_request *request); @@ -588,18 +621,12 @@ static int refresh_lock(struct remote_lock *lock) { struct active_request_slot *slot; struct slot_results results; - char *if_header; - char timeout_header[25]; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers; int rc = 0; lock->refreshing = 1; - if_header = xmalloc(strlen(lock->token) + 25); - sprintf(if_header, "If: (<%s>)", lock->token); - sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout); - dav_headers = curl_slist_append(dav_headers, if_header); - dav_headers = curl_slist_append(dav_headers, timeout_header); + dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF | DAV_HEADER_TIMEOUT); slot = get_active_slot(); slot->results = &results; @@ -622,7 +649,6 @@ static int refresh_lock(struct remote_lock *lock) lock->refreshing = 0; curl_slist_free_all(dav_headers); - free(if_header); return rc; } @@ -1303,14 +1329,10 @@ static int unlock_remote(struct remote_lock *lock) struct active_request_slot *slot; struct slot_results results; struct remote_lock *prev = remote->locks; - char *lock_token_header; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers; int rc = 0; - lock_token_header = xmalloc(strlen(lock->token) + 31); - sprintf(lock_token_header, "Lock-Token: <%s>", - lock->token); - dav_headers = curl_slist_append(dav_headers, lock_token_header); + dav_headers = get_dav_token_headers(lock, DAV_HEADER_LOCK); slot = get_active_slot(); slot->results = &results; @@ -1331,7 +1353,6 @@ static int unlock_remote(struct remote_lock *lock) } curl_slist_free_all(dav_headers); - free(lock_token_header); if (remote->locks == lock) { remote->locks = lock->next; @@ -1364,7 +1385,7 @@ static void remove_locks(void) static void remove_locks_on_signal(int signo) { remove_locks(); - signal(signo, SIG_DFL); + sigchain_pop(signo); raise(signo); } @@ -1731,13 +1752,10 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) { struct active_request_slot *slot; struct slot_results results; - char *if_header; struct buffer out_buffer = { STRBUF_INIT, 0 }; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers; - if_header = xmalloc(strlen(lock->token) + 25); - sprintf(if_header, "If: (<%s>)", lock->token); - dav_headers = curl_slist_append(dav_headers, if_header); + dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF); strbuf_addf(&out_buffer.buf, "%s\n", sha1_to_hex(sha1)); @@ -1756,7 +1774,6 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) if (start_active_slot(slot)) { run_active_slot(slot); strbuf_release(&out_buffer.buf); - free(if_header); if (results.curl_result != CURLE_OK) { fprintf(stderr, "PUT error: curl result=%d, HTTP code=%ld\n", @@ -1766,7 +1783,6 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) } } else { strbuf_release(&out_buffer.buf); - free(if_header); fprintf(stderr, "Unable to start PUT request\n"); return 0; } @@ -1948,15 +1964,12 @@ static void update_remote_info_refs(struct remote_lock *lock) struct buffer buffer = { STRBUF_INIT, 0 }; struct active_request_slot *slot; struct slot_results results; - char *if_header; - struct curl_slist *dav_headers = NULL; + struct curl_slist *dav_headers; remote_ls("refs/", (PROCESS_FILES | RECURSIVE), add_remote_info_ref, &buffer.buf); if (!aborted) { - if_header = xmalloc(strlen(lock->token) + 25); - sprintf(if_header, "If: (<%s>)", lock->token); - dav_headers = curl_slist_append(dav_headers, if_header); + dav_headers = get_dav_token_headers(lock, DAV_HEADER_IF); slot = get_active_slot(); slot->results = &results; @@ -1978,7 +1991,6 @@ static void update_remote_info_refs(struct remote_lock *lock) results.curl_result, results.http_code); } } - free(if_header); } strbuf_release(&buffer.buf); } @@ -2184,6 +2196,8 @@ int main(int argc, char **argv) struct ref *ref; char *rewritten_url = NULL; + git_extract_argv0_path(argv[0]); + setup_git_directory(); remote = xcalloc(sizeof(*remote), 1); @@ -2266,10 +2280,7 @@ int main(int argc, char **argv) goto cleanup; } - signal(SIGINT, remove_locks_on_signal); - signal(SIGHUP, remove_locks_on_signal); - signal(SIGQUIT, remove_locks_on_signal); - signal(SIGTERM, remove_locks_on_signal); + sigchain_push_common(remove_locks_on_signal); /* Check whether the remote has server info files */ remote->can_update_info_refs = 0; diff --git a/imap-send.c b/imap-send.c index c3fa0df85..f91293c23 100644 --- a/imap-send.c +++ b/imap-send.c @@ -23,6 +23,7 @@ */ #include "cache.h" +#include "exec_cmd.h" #ifdef NO_OPENSSL typedef void *SSL; #endif @@ -1389,6 +1390,8 @@ int main(int argc, char **argv) int total, n = 0; int nongit_ok; + git_extract_argv0_path(argv[0]); + /* init the random number generator */ arc4_init(); diff --git a/index-pack.c b/index-pack.c index b46a6d659..f7a38079e 100644 --- a/index-pack.c +++ b/index-pack.c @@ -8,6 +8,7 @@ #include "tree.h" #include "progress.h" #include "fsck.h" +#include "exec_cmd.h" static const char index_pack_usage[] = "git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }"; @@ -880,6 +881,8 @@ int main(int argc, char **argv) struct pack_idx_entry **idx_objects; unsigned char pack_sha1[20]; + git_extract_argv0_path(argv[0]); + /* * We wish to read the repository's config file if any, and * for that it is necessary to call setup_git_directory_gently(). diff --git a/lockfile.c b/lockfile.c index 858915553..021c3375c 100644 --- a/lockfile.c +++ b/lockfile.c @@ -2,6 +2,7 @@ * Copyright (c) 2005, Junio C Hamano */ #include "cache.h" +#include "sigchain.h" static struct lock_file *lock_file_list; static const char *alternate_index_output; @@ -24,7 +25,7 @@ static void remove_lock_file(void) static void remove_lock_file_on_signal(int signo) { remove_lock_file(); - signal(signo, SIG_DFL); + sigchain_pop(signo); raise(signo); } @@ -136,11 +137,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) lk->fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666); if (0 <= lk->fd) { if (!lock_file_list) { - signal(SIGINT, remove_lock_file_on_signal); - signal(SIGHUP, remove_lock_file_on_signal); - signal(SIGTERM, remove_lock_file_on_signal); - signal(SIGQUIT, remove_lock_file_on_signal); - signal(SIGPIPE, remove_lock_file_on_signal); + sigchain_push_common(remove_lock_file_on_signal); atexit(remove_lock_file); } lk->owner = getpid(); diff --git a/merge-index.c b/merge-index.c index 7827e87a9..aa9cf23a3 100644 --- a/merge-index.c +++ b/merge-index.c @@ -1,5 +1,6 @@ #include "cache.h" #include "run-command.h" +#include "exec_cmd.h" static const char *pgm; static const char *arguments[9]; @@ -91,7 +92,9 @@ int main(int argc, char **argv) signal(SIGCHLD, SIG_DFL); if (argc < 3) - usage("git-merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)"); + usage("git merge-index [-o] [-q] <merge-program> (-a | [--] <filename>*)"); + + git_extract_argv0_path(argv[0]); setup_git_directory(); read_cache(); diff --git a/merge-tree.c b/merge-tree.c index 2d1413efb..f01e7c81a 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -2,8 +2,9 @@ #include "tree-walk.h" #include "xdiff-interface.h" #include "blob.h" +#include "exec_cmd.h" -static const char merge_tree_usage[] = "git-merge-tree <base-tree> <branch1> <branch2>"; +static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>"; static int resolve_directories = 1; struct merge_list { @@ -344,6 +345,8 @@ int main(int argc, char **argv) if (argc != 4) usage(merge_tree_usage); + git_extract_argv0_path(argv[0]); + setup_git_directory(); buf1 = get_tree_descriptor(t+0, argv[1]); @@ -1,5 +1,6 @@ #include "cache.h" #include "tag.h" +#include "exec_cmd.h" /* * A signature file has a very simple fixed format: four lines @@ -157,7 +158,9 @@ int main(int argc, char **argv) unsigned char result_sha1[20]; if (argc != 1) - usage("git-mktag < signaturefile"); + usage("git mktag < signaturefile"); + + git_extract_argv0_path(argv[0]); setup_git_directory(); @@ -6,6 +6,7 @@ #include "cache.h" #include "quote.h" #include "tree.h" +#include "exec_cmd.h" static struct treeent { unsigned mode; @@ -61,7 +62,7 @@ static void write_tree(unsigned char *sha1) write_sha1_file(buf.buf, buf.len, tree_type, sha1); } -static const char mktree_usage[] = "git-mktree [-z]"; +static const char mktree_usage[] = "git mktree [-z]"; int main(int ac, char **av) { @@ -70,6 +71,8 @@ int main(int ac, char **av) unsigned char sha1[20]; int line_termination = '\n'; + git_extract_argv0_path(av[0]); + setup_git_directory(); while ((1 < ac) && av[1][0] == '-') { @@ -268,3 +268,22 @@ void add_object_array_with_mode(struct object *obj, const char *name, struct obj objects[nr].mode = mode; array->nr = ++nr; } + +void object_array_remove_duplicates(struct object_array *array) +{ + int ref, src, dst; + struct object_array_entry *objects = array->objects; + + for (ref = 0; ref < array->nr - 1; ref++) { + for (src = ref + 1, dst = src; + src < array->nr; + src++) { + if (!strcmp(objects[ref].name, objects[src].name)) + continue; + if (src != dst) + objects[dst] = objects[src]; + dst++; + } + array->nr = dst; + } +} @@ -82,5 +82,6 @@ int object_list_contains(struct object_list *list, struct object *obj); /* Object array handling .. */ void add_object_array(struct object *obj, const char *name, struct object_array *array); void add_object_array_with_mode(struct object *obj, const char *name, struct object_array *array, unsigned mode); +void object_array_remove_duplicates(struct object_array *); #endif /* OBJECT_H */ diff --git a/pack-redundant.c b/pack-redundant.c index e93eb966e..48a12bc13 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -7,6 +7,7 @@ */ #include "cache.h" +#include "exec_cmd.h" #define BLKSIZE 512 @@ -601,6 +602,8 @@ int main(int argc, char **argv) unsigned char *sha1; char buf[42]; /* 40 byte sha1 + \n + \0 */ + git_extract_argv0_path(argv[0]); + setup_git_directory(); for (i = 1; i < argc; i++) { @@ -1,5 +1,6 @@ #include "cache.h" #include "run-command.h" +#include "sigchain.h" /* * This is split up from the rest of git so that we can do @@ -38,6 +39,13 @@ static void wait_for_pager(void) finish_command(&pager_process); } +static void wait_for_pager_signal(int signo) +{ + wait_for_pager(); + sigchain_pop(signo); + raise(signo); +} + void setup_pager(void) { const char *pager = getenv("GIT_PAGER"); @@ -75,6 +83,7 @@ void setup_pager(void) close(pager_process.in); /* this makes sure that the parent terminates after the pager */ + sigchain_push_common(wait_for_pager_signal); atexit(wait_for_pager); } diff --git a/parse-options.c b/parse-options.c index 9eb55cc8b..4c5d09dd2 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1,6 +1,7 @@ #include "git-compat-util.h" #include "parse-options.h" #include "cache.h" +#include "commit.h" #define OPT_SHORT 1 #define OPT_UNSET 2 @@ -506,6 +507,22 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg, return 0; } +int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) +{ + unsigned char sha1[20]; + struct commit *commit; + + if (!arg) + return -1; + if (get_sha1(arg, sha1)) + return error("malformed object name %s", arg); + commit = lookup_commit_reference(sha1); + if (!commit) + return error("no such commit %s", arg); + commit_list_insert(commit, opt->value); + return 0; +} + /* * This should really be OPTION_FILENAME type as a part of * parse_options that take prefix to do this while parsing. diff --git a/parse-options.h b/parse-options.h index 034162ec6..912290549 100644 --- a/parse-options.h +++ b/parse-options.h @@ -151,6 +151,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx); extern int parse_opt_abbrev_cb(const struct option *, const char *, int); extern int parse_opt_approxidate_cb(const struct option *, const char *, int); extern int parse_opt_verbosity_cb(const struct option *, const char *, int); +extern int parse_opt_with_commit(const struct option *, const char *, int); #define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose") #define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet") diff --git a/patch-id.c b/patch-id.c index 871f1d20c..0df4cb086 100644 --- a/patch-id.c +++ b/patch-id.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "exec_cmd.h" static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c) { @@ -72,13 +73,15 @@ static void generate_id_list(void) flush_current_id(patchlen, sha1, &ctx); } -static const char patch_id_usage[] = "git-patch-id < patch"; +static const char patch_id_usage[] = "git patch-id < patch"; int main(int argc, char **argv) { if (argc != 1) usage(patch_id_usage); + git_extract_argv0_path(argv[0]); + generate_id_list(); return 0; } diff --git a/read-cache.c b/read-cache.c index b1475ffa0..940ec76fd 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1574,6 +1574,26 @@ static void update_callback(struct diff_queue_struct *q, default: die("unexpected diff status %c", p->status); case DIFF_STATUS_UNMERGED: + /* + * ADD_CACHE_IGNORE_REMOVAL is unset if "git + * add -u" is calling us, In such a case, a + * missing work tree file needs to be removed + * if there is an unmerged entry at stage #2, + * but such a diff record is followed by + * another with DIFF_STATUS_DELETED (and if + * there is no stage #2, we won't see DELETED + * nor MODIFIED). We can simply continue + * either way. + */ + if (!(data->flags & ADD_CACHE_IGNORE_REMOVAL)) + continue; + /* + * Otherwise, it is "git add path" is asking + * to explicitly add it; we fall through. A + * missing work tree file is an error and is + * caught by add_file_to_index() in such a + * case. + */ case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: if (add_file_to_index(&the_index, path, data->flags)) { @@ -1453,7 +1453,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * return 1; } -int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long ofs, void *cb_data) { const char *logfile; FILE *logfp; @@ -1464,6 +1464,16 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) logfp = fopen(logfile, "r"); if (!logfp) return -1; + + if (ofs) { + struct stat statbuf; + if (fstat(fileno(logfp), &statbuf) || + statbuf.st_size < ofs || + fseek(logfp, -ofs, SEEK_END) || + fgets(buf, sizeof(buf), logfp)) + return -1; + } + while (fgets(buf, sizeof(buf), logfp)) { unsigned char osha1[20], nsha1[20]; char *email_end, *message; @@ -1497,6 +1507,11 @@ int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) return ret; } +int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data) +{ + return for_each_recent_reflog_ent(ref, fn, 0, cb_data); +} + static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data) { DIR *dir = opendir(git_path("logs/%s", base)); @@ -60,6 +60,7 @@ extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned /* iterate over reflog entries */ typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *); int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data); +int for_each_recent_reflog_ent(const char *ref, each_reflog_ent_fn fn, long, void *cb_data); /* * Calls the specified function for each reflog file until it returns nonzero, diff --git a/revision.c b/revision.c index db60f06c9..8603c1458 100644 --- a/revision.c +++ b/revision.c @@ -183,8 +183,11 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object if (!tag->tagged) die("bad tag"); object = parse_object(tag->tagged->sha1); - if (!object) + if (!object) { + if (flags & UNINTERESTING) + return NULL; die("bad object %s", sha1_to_hex(tag->tagged->sha1)); + } } /* @@ -479,9 +482,10 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, while (parent) { struct commit *p = parent->item; parent = parent->next; + if (p) + p->object.flags |= UNINTERESTING; if (parse_commit(p) < 0) - return -1; - p->object.flags |= UNINTERESTING; + continue; if (p->parents) mark_parents_uninteresting(p); if (p->object.flags & SEEN) @@ -1263,6 +1267,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (!strcmp(arg, "--all")) { handle_refs(revs, flags, for_each_ref); + handle_refs(revs, flags, head_ref); continue; } if (!strcmp(arg, "--branches")) { diff --git a/run-command.c b/run-command.c index db9ce5920..b05c734d0 100644 --- a/run-command.c +++ b/run-command.c @@ -118,7 +118,9 @@ int start_command(struct child_process *cmd) } else { execvp(cmd->argv[0], (char *const*) cmd->argv); } - die("exec %s failed.", cmd->argv[0]); + trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0], + strerror(errno)); + exit(127); } #else int s0 = -1, s1 = -1, s2 = -1; /* backups of stdin, stdout, stderr */ @@ -187,6 +189,7 @@ 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) @@ -197,7 +200,9 @@ int start_command(struct child_process *cmd) close(cmd->out); if (need_err) close_pair(fderr); - return -ERR_RUN_COMMAND_FORK; + return err == ENOENT ? + -ERR_RUN_COMMAND_EXEC : + -ERR_RUN_COMMAND_FORK; } if (need_in) @@ -236,9 +241,14 @@ static int wait_or_whine(pid_t pid) if (!WIFEXITED(status)) return -ERR_RUN_COMMAND_WAITPID_NOEXIT; code = WEXITSTATUS(status); - if (code) + switch (code) { + case 127: + return -ERR_RUN_COMMAND_EXEC; + case 0: + return 0; + default: return -code; - return 0; + } } } diff --git a/run-command.h b/run-command.h index 0211e1d47..15e870a65 100644 --- a/run-command.h +++ b/run-command.h @@ -10,6 +10,7 @@ enum { 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; diff --git a/sha1_file.c b/sha1_file.c index 360f7e5a0..8868b800c 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -2340,7 +2340,8 @@ static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename) static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, void *buf, unsigned long len, time_t mtime) { - int fd, size, ret; + int fd, ret; + size_t size; unsigned char *compressed; z_stream stream; char *filename; diff --git a/sha1_name.c b/sha1_name.c index 159c2ab84..5d0ac0263 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -238,8 +238,28 @@ static int ambiguous_path(const char *path, int len) return slash; } +/* + * *string and *len will only be substituted, and *string returned (for + * later free()ing) if the string passed in is of the form @{-<n>}. + */ +static char *substitute_nth_last_branch(const char **string, int *len) +{ + struct strbuf buf = STRBUF_INIT; + int ret = interpret_nth_last_branch(*string, &buf); + + if (ret == *len) { + size_t size; + *string = strbuf_detach(&buf, &size); + *len = size; + return (char *)*string; + } + + return NULL; +} + int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { + char *last_branch = substitute_nth_last_branch(&str, &len); const char **p, *r; int refs_found = 0; @@ -259,11 +279,13 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) break; } } + free(last_branch); return refs_found; } int dwim_log(const char *str, int len, unsigned char *sha1, char **log) { + char *last_branch = substitute_nth_last_branch(&str, &len); const char **p; int logs_found = 0; @@ -294,9 +316,12 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log) if (!warn_ambiguous_refs) break; } + free(last_branch); return logs_found; } +static int get_sha1_1(const char *name, int len, unsigned char *sha1); + static int get_sha1_basic(const char *str, int len, unsigned char *sha1) { static const char *warning = "warning: refname '%.*s' is ambiguous.\n"; @@ -307,10 +332,10 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) if (len == 40 && !get_sha1_hex(str, sha1)) return 0; - /* basic@{time or number} format to query ref-log */ + /* basic@{time or number or -number} format to query ref-log */ reflog_len = at = 0; - if (str[len-1] == '}') { - for (at = 0; at < len - 1; at++) { + if (len && str[len-1] == '}') { + for (at = len-2; at >= 0; at--) { if (str[at] == '@' && str[at+1] == '{') { reflog_len = (len-1) - (at+2); len = at; @@ -324,6 +349,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return -1; if (!len && reflog_len) { + struct strbuf buf = STRBUF_INIT; + int ret; + /* try the @{-N} syntax for n-th checkout */ + ret = interpret_nth_last_branch(str+at, &buf); + if (ret > 0) { + /* substitute this branch name and restart */ + return get_sha1_1(buf.buf, buf.len, sha1); + } else if (ret == 0) { + return -1; + } /* allow "@{...}" to mean the current branch reflog */ refs_found = dwim_ref("HEAD", 4, sha1, &real_ref); } else if (reflog_len) @@ -379,8 +414,6 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) return 0; } -static int get_sha1_1(const char *name, int len, unsigned char *sha1); - static int get_parent(const char *name, int len, unsigned char *result, int idx) { @@ -674,6 +707,92 @@ static int get_sha1_oneline(const char *prefix, unsigned char *sha1) return retval; } +struct grab_nth_branch_switch_cbdata { + long cnt, alloc; + struct strbuf *buf; +}; + +static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, + const char *email, unsigned long timestamp, int tz, + const char *message, void *cb_data) +{ + struct grab_nth_branch_switch_cbdata *cb = cb_data; + const char *match = NULL, *target = NULL; + size_t len; + int nth; + + if (!prefixcmp(message, "checkout: moving from ")) { + match = message + strlen("checkout: moving from "); + target = strstr(match, " to "); + } + + if (!match || !target) + return 0; + + len = target - match; + nth = cb->cnt++ % cb->alloc; + strbuf_reset(&cb->buf[nth]); + strbuf_add(&cb->buf[nth], match, len); + return 0; +} + +/* + * This reads "@{-N}" syntax, finds the name of the Nth previous + * branch we were on, and places the name of the branch in the given + * buf and returns the number of characters parsed if successful. + * + * If the input is not of the accepted format, it returns a negative + * number to signal an error. + * + * If the input was ok but there are not N branch switches in the + * reflog, it returns 0. + */ +int interpret_nth_last_branch(const char *name, struct strbuf *buf) +{ + long nth; + int i, retval; + struct grab_nth_branch_switch_cbdata cb; + const char *brace; + char *num_end; + + if (name[0] != '@' || name[1] != '{' || name[2] != '-') + return -1; + brace = strchr(name, '}'); + if (!brace) + return -1; + nth = strtol(name+3, &num_end, 10); + if (num_end != brace) + return -1; + if (nth <= 0) + return -1; + cb.alloc = nth; + cb.buf = xmalloc(nth * sizeof(struct strbuf)); + for (i = 0; i < nth; i++) + strbuf_init(&cb.buf[i], 20); + cb.cnt = 0; + retval = 0; + for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb); + if (cb.cnt < nth) { + cb.cnt = 0; + for (i = 0; i < nth; i++) + strbuf_release(&cb.buf[i]); + for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb); + } + if (cb.cnt < nth) + goto release_return; + i = cb.cnt % nth; + strbuf_reset(buf); + strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len); + retval = brace-name+1; + +release_return: + for (i = 0; i < nth; i++) + strbuf_release(&cb.buf[i]); + free(cb.buf); + + return retval; +} + /* * This is like "get_sha1_basic()", except it allows "sha1 expressions", * notably "xyz^" for "parent of xyz" diff --git a/sigchain.c b/sigchain.c new file mode 100644 index 000000000..1118b99e5 --- /dev/null +++ b/sigchain.c @@ -0,0 +1,52 @@ +#include "sigchain.h" +#include "cache.h" + +#define SIGCHAIN_MAX_SIGNALS 32 + +struct sigchain_signal { + sigchain_fun *old; + int n; + int alloc; +}; +static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS]; + +static void check_signum(int sig) +{ + if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS) + die("BUG: signal out of range: %d", sig); +} + +int sigchain_push(int sig, sigchain_fun f) +{ + struct sigchain_signal *s = signals + sig; + check_signum(sig); + + ALLOC_GROW(s->old, s->n + 1, s->alloc); + s->old[s->n] = signal(sig, f); + if (s->old[s->n] == SIG_ERR) + return -1; + s->n++; + return 0; +} + +int sigchain_pop(int sig) +{ + struct sigchain_signal *s = signals + sig; + check_signum(sig); + if (s->n < 1) + return 0; + + if (signal(sig, s->old[s->n - 1]) == SIG_ERR) + return -1; + s->n--; + return 0; +} + +void sigchain_push_common(sigchain_fun f) +{ + sigchain_push(SIGINT, f); + sigchain_push(SIGHUP, f); + sigchain_push(SIGTERM, f); + sigchain_push(SIGQUIT, f); + sigchain_push(SIGPIPE, f); +} diff --git a/sigchain.h b/sigchain.h new file mode 100644 index 000000000..618083bce --- /dev/null +++ b/sigchain.h @@ -0,0 +1,11 @@ +#ifndef SIGCHAIN_H +#define SIGCHAIN_H + +typedef void (*sigchain_fun)(int); + +int sigchain_push(int sig, sigchain_fun f); +int sigchain_pop(int sig); + +void sigchain_push_common(sigchain_fun f); + +#endif /* SIGCHAIN_H */ diff --git a/symlinks.c b/symlinks.c index 5a5e781a1..f262b7c44 100644 --- a/symlinks.c +++ b/symlinks.c @@ -1,64 +1,241 @@ #include "cache.h" -struct pathname { +static struct cache_def { + char path[PATH_MAX + 1]; int len; - char path[PATH_MAX]; -}; + int flags; + int track_flags; + int prefix_len_stat_func; +} cache; -/* Return matching pathname prefix length, or zero if not matching */ -static inline int match_pathname(int len, const char *name, struct pathname *match) +/* + * Returns the length (on a path component basis) of the longest + * common prefix match of 'name' and the cached path string. + */ +static inline int longest_match_lstat_cache(int len, const char *name, + int *previous_slash) { - int match_len = match->len; - return (len > match_len && - name[match_len] == '/' && - !memcmp(name, match->path, match_len)) ? match_len : 0; + int max_len, match_len = 0, match_len_prev = 0, i = 0; + + max_len = len < cache.len ? len : cache.len; + while (i < max_len && name[i] == cache.path[i]) { + if (name[i] == '/') { + match_len_prev = match_len; + match_len = i; + } + i++; + } + /* Is the cached path string a substring of 'name'? */ + if (i == cache.len && cache.len < len && name[cache.len] == '/') { + match_len_prev = match_len; + match_len = cache.len; + /* Is 'name' a substring of the cached path string? */ + } else if ((i == len && len < cache.len && cache.path[len] == '/') || + (i == len && len == cache.len)) { + match_len_prev = match_len; + match_len = len; + } + *previous_slash = match_len_prev; + return match_len; } -static inline void set_pathname(int len, const char *name, struct pathname *match) +static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func) { - if (len < PATH_MAX) { - match->len = len; - memcpy(match->path, name, len); - match->path[len] = 0; - } + cache.path[0] = '\0'; + cache.len = 0; + cache.flags = 0; + cache.track_flags = track_flags; + cache.prefix_len_stat_func = prefix_len_stat_func; } -int has_symlink_leading_path(int len, const char *name) +#define FL_DIR (1 << 0) +#define FL_NOENT (1 << 1) +#define FL_SYMLINK (1 << 2) +#define FL_LSTATERR (1 << 3) +#define FL_ERR (1 << 4) +#define FL_FULLPATH (1 << 5) + +/* + * Check if name 'name' of length 'len' has a symlink leading + * component, or if the directory exists and is real, or not. + * + * To speed up the check, some information is allowed to be cached. + * This can be indicated by the 'track_flags' argument, which also can + * be used to indicate that we should check the full path. + * + * The 'prefix_len_stat_func' parameter can be used to set the length + * of the prefix, where the cache should use the stat() function + * instead of the lstat() function to test each path component. + */ +static int lstat_cache(int len, const char *name, + int track_flags, int prefix_len_stat_func) { - static struct pathname link, nonlink; - char path[PATH_MAX]; + int match_len, last_slash, last_slash_dir, previous_slash; + int match_flags, ret_flags, save_flags, max_len, ret; struct stat st; - char *sp; - int known_dir; - /* - * See if the last known symlink cache matches. - */ - if (match_pathname(len, name, &link)) - return 1; + if (cache.track_flags != track_flags || + cache.prefix_len_stat_func != prefix_len_stat_func) { + /* + * As a safeguard we clear the cache if the values of + * track_flags and/or prefix_len_stat_func does not + * match with the last supplied values. + */ + reset_lstat_cache(track_flags, prefix_len_stat_func); + match_len = last_slash = 0; + } else { + /* + * Check to see if we have a match from the cache for + * the 2 "excluding" path types. + */ + match_len = last_slash = + longest_match_lstat_cache(len, name, &previous_slash); + match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK); + if (match_flags && match_len == cache.len) + return match_flags; + /* + * If we now have match_len > 0, we would know that + * the matched part will always be a directory. + * + * Also, if we are tracking directories and 'name' is + * a substring of the cache on a path component basis, + * we can return immediately. + */ + match_flags = track_flags & FL_DIR; + if (match_flags && len == match_len) + return match_flags; + } /* - * Get rid of the last known directory part + * Okay, no match from the cache so far, so now we have to + * check the rest of the path components. */ - known_dir = match_pathname(len, name, &nonlink); - - while ((sp = strchr(name + known_dir + 1, '/')) != NULL) { - int thislen = sp - name ; - memcpy(path, name, thislen); - path[thislen] = 0; - - if (lstat(path, &st)) - return 0; - if (S_ISDIR(st.st_mode)) { - set_pathname(thislen, path, &nonlink); - known_dir = thislen; + ret_flags = FL_DIR; + last_slash_dir = last_slash; + max_len = len < PATH_MAX ? len : PATH_MAX; + while (match_len < max_len) { + do { + cache.path[match_len] = name[match_len]; + match_len++; + } while (match_len < max_len && name[match_len] != '/'); + if (match_len >= max_len && !(track_flags & FL_FULLPATH)) + break; + last_slash = match_len; + cache.path[last_slash] = '\0'; + + if (last_slash <= prefix_len_stat_func) + ret = stat(cache.path, &st); + else + ret = lstat(cache.path, &st); + + if (ret) { + ret_flags = FL_LSTATERR; + if (errno == ENOENT) + ret_flags |= FL_NOENT; + } else if (S_ISDIR(st.st_mode)) { + last_slash_dir = last_slash; continue; - } - if (S_ISLNK(st.st_mode)) { - set_pathname(thislen, path, &link); - return 1; + } else if (S_ISLNK(st.st_mode)) { + ret_flags = FL_SYMLINK; + } else { + ret_flags = FL_ERR; } break; } - return 0; + + /* + * At the end update the cache. Note that max 3 different + * path types, FL_NOENT, FL_SYMLINK and FL_DIR, can be cached + * for the moment! + */ + save_flags = ret_flags & track_flags & (FL_NOENT|FL_SYMLINK); + if (save_flags && last_slash > 0 && last_slash <= PATH_MAX) { + cache.path[last_slash] = '\0'; + cache.len = last_slash; + cache.flags = save_flags; + } else if (track_flags & FL_DIR && + last_slash_dir > 0 && last_slash_dir <= PATH_MAX) { + /* + * We have a separate test for the directory case, + * since it could be that we have found a symlink or a + * non-existing directory and the track_flags says + * that we cannot cache this fact, so the cache would + * then have been left empty in this case. + * + * But if we are allowed to track real directories, we + * can still cache the path components before the last + * one (the found symlink or non-existing component). + */ + cache.path[last_slash_dir] = '\0'; + cache.len = last_slash_dir; + cache.flags = FL_DIR; + } else { + reset_lstat_cache(track_flags, prefix_len_stat_func); + } + return ret_flags; +} + +/* + * Invalidate the given 'name' from the cache, if 'name' matches + * completely with the cache. + */ +void invalidate_lstat_cache(int len, const char *name) +{ + int match_len, previous_slash; + + match_len = longest_match_lstat_cache(len, name, &previous_slash); + if (len == match_len) { + if ((cache.track_flags & FL_DIR) && previous_slash > 0) { + cache.path[previous_slash] = '\0'; + cache.len = previous_slash; + cache.flags = FL_DIR; + } else + reset_lstat_cache(cache.track_flags, + cache.prefix_len_stat_func); + } +} + +/* + * Completely clear the contents of the cache + */ +void clear_lstat_cache(void) +{ + reset_lstat_cache(0, 0); +} + +#define USE_ONLY_LSTAT 0 + +/* + * Return non-zero if path 'name' has a leading symlink component + */ +int has_symlink_leading_path(int len, const char *name) +{ + return lstat_cache(len, name, + FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) & + FL_SYMLINK; +} + +/* + * Return non-zero if path 'name' has a leading symlink component or + * if some leading path component does not exists. + */ +int has_symlink_or_noent_leading_path(int len, const char *name) +{ + return lstat_cache(len, name, + FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) & + (FL_SYMLINK|FL_NOENT); +} + +/* + * Return non-zero if all path components of 'name' exists as a + * directory. If prefix_len > 0, we will test with the stat() + * function instead of the lstat() function for a prefix length of + * 'prefix_len', thus we then allow for symlinks in the prefix part as + * long as those points to real existing directories. + */ +int has_dirs_only_path(int len, const char *name, int prefix_len) +{ + return lstat_cache(len, name, + FL_DIR|FL_FULLPATH, prefix_len) & + FL_DIR; } @@ -212,6 +212,24 @@ library for your script to use. is to summarize successes and failures in the test script and exit with an appropriate error code. + - test_tick + + Make commit and tag names consistent by setting the author and + committer times to defined stated. Subsequent calls will + advance the times by a fixed amount. + + - test_commit <message> [<filename> [<contents>]] + + Creates a commit with the given message, committing the given + file with the given contents (default for both is to reuse the + message string), and adds a tag (again reusing the message + string as name). Calls test_tick to make the SHA-1s + reproducible. + + - test_merge <message> <commit-or-tag> + + Merges the given rev using the given message. Like test_commit, + creates a tag and calls test_tick before committing. Tips for Writing Tests ---------------------- diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh new file mode 100644 index 000000000..260a23193 --- /dev/null +++ b/t/lib-rebase.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +# After setting the fake editor with this function, you can +# +# - override the commit message with $FAKE_COMMIT_MESSAGE, +# - amend the commit message with $FAKE_COMMIT_AMEND +# - check that non-commit messages have a certain line count with $EXPECT_COUNT +# - rewrite a rebase -i script with $FAKE_LINES in the form +# +# "[<lineno1>] [<lineno2>]..." +# +# If a line number is prefixed with "squash" or "edit", the respective line's +# command will be replaced with the specified one. + +set_fake_editor () { + echo "#!$SHELL_PATH" >fake-editor.sh + cat >> fake-editor.sh <<\EOF +case "$1" in +*/COMMIT_EDITMSG) + test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" + test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" + exit + ;; +esac +test -z "$EXPECT_COUNT" || + test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) || + exit +test -z "$FAKE_LINES" && exit +grep -v '^#' < "$1" > "$1".tmp +rm -f "$1" +cat "$1".tmp +action=pick +for line in $FAKE_LINES; do + case $line in + squash|edit) + action="$line";; + *) + echo sed -n "${line}s/^pick/$action/p" + sed -n "${line}p" < "$1".tmp + sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" + action=pick;; + esac +done +EOF + + test_set_editor "$(pwd)/fake-editor.sh" + chmod a+x fake-editor.sh +} diff --git a/t/t0005-signals.sh b/t/t0005-signals.sh new file mode 100755 index 000000000..09f855af3 --- /dev/null +++ b/t/t0005-signals.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='signals work as we expect' +. ./test-lib.sh + +cat >expect <<EOF +three +two +one +EOF + +test_expect_success 'sigchain works' ' + test-sigchain >actual + case "$?" in + 143) true ;; # POSIX w/ SIGTERM=15 + 3) true ;; # Windows + *) false ;; + esac && + test_cmp expect actual +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh new file mode 100755 index 000000000..4597af0eb --- /dev/null +++ b/t/t1450-fsck.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='git fsck random collection of tests' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A fileA one && + git checkout HEAD^0 && + test_commit B fileB two && + git tag -d A B && + git reflog expire --expire=now --all +' + +test_expect_success 'HEAD is part of refs' ' + test 0 = $(git fsck | wc -l) +' + +test_expect_success 'loose objects borrowed from alternate are not missing' ' + mkdir another && + ( + cd another && + git init && + echo ../../../.git/objects >.git/objects/info/alternates && + test_commit C fileC one && + git fsck >out && + ! grep "missing blob" out + ) +' + +test_done diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh new file mode 100755 index 000000000..d709ecf8d --- /dev/null +++ b/t/t1505-rev-parse-last.sh @@ -0,0 +1,69 @@ +#!/bin/sh + +test_description='test @{-N} syntax' + +. ./test-lib.sh + + +make_commit () { + echo "$1" > "$1" && + git add "$1" && + git commit -m "$1" +} + + +test_expect_success 'setup' ' + + make_commit 1 && + git branch side && + make_commit 2 && + make_commit 3 && + git checkout side && + make_commit 4 && + git merge master && + git checkout master + +' + +# 1 -- 2 -- 3 master +# \ \ +# \ \ +# --- 4 --- 5 side +# +# and 'side' should be the last branch + +test_rev_equivalent () { + + git rev-parse "$1" > expect && + git rev-parse "$2" > output && + test_cmp expect output + +} + +test_expect_success '@{-1} works' ' + test_rev_equivalent side @{-1} +' + +test_expect_success '@{-1}~2 works' ' + test_rev_equivalent side~2 @{-1}~2 +' + +test_expect_success '@{-1}^2 works' ' + test_rev_equivalent side^2 @{-1}^2 +' + +test_expect_success '@{-1}@{1} works' ' + test_rev_equivalent side@{1} @{-1}@{1} +' + +test_expect_success '@{-2} works' ' + test_rev_equivalent master @{-2} +' + +test_expect_success '@{-3} fails' ' + test_must_fail git rev-parse @{-3} +' + +test_done + + diff --git a/t/t2012-checkout-last.sh b/t/t2012-checkout-last.sh new file mode 100755 index 000000000..87b30a268 --- /dev/null +++ b/t/t2012-checkout-last.sh @@ -0,0 +1,94 @@ +#!/bin/sh + +test_description='checkout can switch to last branch' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo hello >world && + git add world && + git commit -m initial && + git branch other && + echo "hello again" >>world && + git add world && + git commit -m second +' + +test_expect_success '"checkout -" does not work initially' ' + test_must_fail git checkout - +' + +test_expect_success 'first branch switch' ' + git checkout other +' + +test_expect_success '"checkout -" switches back' ' + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/master" +' + +test_expect_success '"checkout -" switches forth' ' + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other" +' + +test_expect_success 'detach HEAD' ' + git checkout $(git rev-parse HEAD) +' + +test_expect_success '"checkout -" attaches again' ' + git checkout - && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/other" +' + +test_expect_success '"checkout -" detaches again' ' + git checkout - && + test "z$(git rev-parse HEAD)" = "z$(git rev-parse other)" && + test_must_fail git symbolic-ref HEAD +' + +test_expect_success 'more switches' ' + for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 + do + git checkout -b branch$i + done +' + +more_switches () { + for i in 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 + do + git checkout branch$i + done +} + +test_expect_success 'switch to the last' ' + more_switches && + git checkout @{-1} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch2" +' + +test_expect_success 'switch to second from the last' ' + more_switches && + git checkout @{-2} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch3" +' + +test_expect_success 'switch to third from the last' ' + more_switches && + git checkout @{-3} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch4" +' + +test_expect_success 'switch to fourth from the last' ' + more_switches && + git checkout @{-4} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch5" +' + +test_expect_success 'switch to twelfth from the last' ' + more_switches && + git checkout @{-12} && + test "z$(git symbolic-ref HEAD)" = "zrefs/heads/branch13" +' + +test_done diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index cd9231cf6..b2ddf5ace 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -12,7 +12,7 @@ and issues a git add -u with path limiting on "dir" to add only the updates to dir/sub. Also tested are "git add -u" without limiting, and "git add -u" -without contents changes.' +without contents changes, and other conditions' . ./test-lib.sh @@ -128,4 +128,52 @@ test_expect_success 'add -n -u should not add but just report' ' ' +test_expect_success 'add -u resolves unmerged paths' ' + git reset --hard && + one=$(echo 1 | git hash-object -w --stdin) && + two=$(echo 2 | git hash-object -w --stdin) && + three=$(echo 3 | git hash-object -w --stdin) && + { + for path in path1 path2 + do + echo "100644 $one 1 $path" + echo "100644 $two 2 $path" + echo "100644 $three 3 $path" + done + echo "100644 $one 1 path3" + echo "100644 $one 1 path4" + echo "100644 $one 3 path5" + echo "100644 $one 3 path6" + } | + git update-index --index-info && + echo 3 >path1 && + echo 2 >path3 && + echo 2 >path5 && + git add -u && + git ls-files -s "path?" >actual && + { + echo "100644 $three 0 path1" + echo "100644 $one 1 path3" + echo "100644 $one 1 path4" + echo "100644 $one 3 path5" + echo "100644 $one 3 path6" + } >expect && + test_cmp expect actual && + + # Bonus tests. Explicit resolving + git add path3 path5 && + test_must_fail git add path4 && + test_must_fail git add path6 && + git rm path4 && + git rm path6 && + + git ls-files -s "path?" >actual && + { + echo "100644 $three 0 path1" + echo "100644 $two 0 path3" + echo "100644 $two 0 path5" + } >expect + +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 2cc8e7abe..603b003ed 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -10,6 +10,10 @@ that the result still makes sense. ' . ./test-lib.sh +. ../lib-rebase.sh + +set_fake_editor + # set up two branches like this: # # A - B - C - D - E @@ -61,39 +65,6 @@ test_expect_success 'setup' ' git tag I ' -echo "#!$SHELL_PATH" >fake-editor.sh -cat >> fake-editor.sh <<\EOF -case "$1" in -*/COMMIT_EDITMSG) - test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" - test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" - exit - ;; -esac -test -z "$EXPECT_COUNT" || - test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) || - exit -test -z "$FAKE_LINES" && exit -grep -v '^#' < "$1" > "$1".tmp -rm -f "$1" -cat "$1".tmp -action=pick -for line in $FAKE_LINES; do - case $line in - squash|edit) - action="$line";; - *) - echo sed -n "${line}s/^pick/$action/p" - sed -n "${line}p" < "$1".tmp - sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" - action=pick;; - esac -done -EOF - -test_set_editor "$(pwd)/fake-editor.sh" -chmod a+x fake-editor.sh - test_expect_success 'no changes are a nop' ' git rebase -i F && test "$(git symbolic-ref -q HEAD)" = "refs/heads/branch2" && @@ -462,4 +433,30 @@ test_expect_success 'do "noop" when there is nothing to cherry-pick' ' ' +test_expect_success 'submodule rebase setup' ' + git checkout A && + mkdir sub && + ( + cd sub && git init && >elif && + git add elif && git commit -m "submodule initial" + ) && + echo 1 >file1 && + git add file1 sub + test_tick && + git commit -m "One" && + echo 2 >file1 && + test_tick && + git commit -a -m "Two" && + ( + cd sub && echo 3 >elif && + git commit -a -m "submodule second" + ) && + test_tick && + git commit -a -m "Three changes submodule" +' + +test_expect_success 'submodule rebase -i' ' + FAKE_LINES="1 squash 2 3" git rebase -i A +' + test_done diff --git a/t/t3409-rebase-hook.sh b/t/t3409-rebase-hook.sh index 1f1b85067..098b75507 100755 --- a/t/t3409-rebase-hook.sh +++ b/t/t3409-rebase-hook.sh @@ -118,7 +118,11 @@ test_expect_success 'pre-rebase hook stops rebase (1)' ' test_expect_success 'pre-rebase hook stops rebase (2)' ' git checkout test && git reset --hard side && - EDITOR=true test_must_fail git rebase -i master && + ( + EDITOR=: + export EDITOR + test_must_fail git rebase -i master + ) && test "z$(git symbolic-ref HEAD)" = zrefs/heads/test && test 0 = $(git rev-list HEAD...side | wc -l) ' diff --git a/t/t3410-rebase-preserve-dropped-merges.sh b/t/t3410-rebase-preserve-dropped-merges.sh index 5816415aa..c49143a1a 100755 --- a/t/t3410-rebase-preserve-dropped-merges.sh +++ b/t/t3410-rebase-preserve-dropped-merges.sh @@ -22,47 +22,17 @@ rewritten. # where B, D and G touch the same file. test_expect_success 'setup' ' - : > file1 && - git add file1 && - test_tick && - git commit -m A && - git tag A && - echo 1 > file1 && - test_tick && - git commit -m B file1 && - : > file2 && - git add file2 && - test_tick && - git commit -m C && - echo 2 > file1 && - test_tick && - git commit -m D file1 && - : > file3 && - git add file3 && - test_tick && - git commit -m E && - git tag E && - git checkout -b branch1 A && - : > file4 && - git add file4 && - test_tick && - git commit -m F && - git tag F && - echo 3 > file1 && - test_tick && - git commit -m G file1 && - git tag G && - : > file5 && - git add file5 && - test_tick && - git commit -m H && - git tag H && - git checkout -b branch2 F && - : > file6 && - git add file6 && - test_tick && - git commit -m I && - git tag I + test_commit A file1 && + test_commit B file1 1 && + test_commit C file2 && + test_commit D file1 2 && + test_commit E file3 && + git checkout A && + test_commit F file4 && + test_commit G file1 3 && + test_commit H file5 && + git checkout F && + test_commit I file6 ' # A - B - C - D - E @@ -72,68 +42,44 @@ test_expect_success 'setup' ' # I -- G2 -- J -- K I -- K # G2 = same changes as G test_expect_success 'skip same-resolution merges with -p' ' - git checkout branch1 && + git checkout H && ! git merge E && - echo 23 > file1 && - git add file1 && - git commit -m L && - git checkout branch2 && - echo 3 > file1 && - git commit -a -m G2 && + test_commit L file1 23 && + git checkout I && + test_commit G2 file1 3 && ! git merge E && - echo 23 > file1 && - git add file1 && - git commit -m J && - echo file7 > file7 && - git add file7 && - git commit -m K && - GIT_EDITOR=: git rebase -i -p branch1 && - test $(git rev-parse branch2^^) = $(git rev-parse branch1) && + test_commit J file1 23 && + test_commit K file7 file7 && + git rebase -i -p L && + test $(git rev-parse HEAD^^) = $(git rev-parse L) && test "23" = "$(cat file1)" && - test "" = "$(cat file6)" && - test "file7" = "$(cat file7)" && - - git checkout branch1 && - git reset --hard H && - git checkout branch2 && - git reset --hard I + test "I" = "$(cat file6)" && + test "file7" = "$(cat file7)" ' # A - B - C - D - E # \ \ \ -# F - G - H -- L \ --> L -# \ | \ -# I -- G2 -- J -- K I -- G2 -- K +# F - G - H -- L2 \ --> L2 +# \ | \ +# I -- G3 --- J2 -- K2 I -- G3 -- K2 # G2 = different changes as G test_expect_success 'keep different-resolution merges with -p' ' - git checkout branch1 && + git checkout H && ! git merge E && - echo 23 > file1 && - git add file1 && - git commit -m L && - git checkout branch2 && - echo 4 > file1 && - git commit -a -m G2 && + test_commit L2 file1 23 && + git checkout I && + test_commit G3 file1 4 && ! git merge E && - echo 24 > file1 && - git add file1 && - git commit -m J && - echo file7 > file7 && - git add file7 && - git commit -m K && - ! GIT_EDITOR=: git rebase -i -p branch1 && + test_commit J2 file1 24 && + test_commit K2 file7 file7 && + test_must_fail git rebase -i -p L2 && echo 234 > file1 && git add file1 && - GIT_EDITOR=: git rebase --continue && - test $(git rev-parse branch2^^^) = $(git rev-parse branch1) && + git rebase --continue && + test $(git rev-parse HEAD^^^) = $(git rev-parse L2) && test "234" = "$(cat file1)" && - test "" = "$(cat file6)" && - test "file7" = "$(cat file7)" && - - git checkout branch1 && - git reset --hard H && - git checkout branch2 && - git reset --hard I + test "I" = "$(cat file6)" && + test "file7" = "$(cat file7)" ' test_done diff --git a/t/t3411-rebase-preserve-around-merges.sh b/t/t3411-rebase-preserve-around-merges.sh index aacfaae84..653350521 100755 --- a/t/t3411-rebase-preserve-around-merges.sh +++ b/t/t3411-rebase-preserve-around-merges.sh @@ -5,44 +5,14 @@ test_description='git rebase preserve merges -This test runs git rebase with and tries to squash a commit from after a merge -to before the merge. +This test runs git rebase with -p and tries to squash a commit from after +a merge to before the merge. ' . ./test-lib.sh -# Copy/paste from t3404-rebase-interactive.sh -echo "#!$SHELL_PATH" >fake-editor.sh -cat >> fake-editor.sh <<\EOF -case "$1" in -*/COMMIT_EDITMSG) - test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" - test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" - exit - ;; -esac -test -z "$EXPECT_COUNT" || - test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) || - exit -test -z "$FAKE_LINES" && exit -grep -v '^#' < "$1" > "$1".tmp -rm -f "$1" -cat "$1".tmp -action=pick -for line in $FAKE_LINES; do - case $line in - squash|edit) - action="$line";; - *) - echo sed -n "${line}s/^pick/$action/p" - sed -n "${line}p" < "$1".tmp - sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1" - action=pick;; - esac -done -EOF +. ../lib-rebase.sh -test_set_editor "$(pwd)/fake-editor.sh" -chmod a+x fake-editor.sh +set_fake_editor # set up two branches like this: # @@ -51,27 +21,13 @@ chmod a+x fake-editor.sh # -- C1 -- test_expect_success 'setup' ' - touch a && - touch b && - git add a && - git commit -m A1 && - git tag A1 - git add b && - git commit -m B1 && - git tag B1 && - git checkout -b branch && - touch c && - git add c && - git commit -m C1 && - git checkout master && - touch d && - git add d && - git commit -m D1 && - git merge branch && - touch f && - git add f && - git commit -m F1 && - git tag F1 + test_commit A1 && + test_commit B1 && + test_commit C1 && + git reset --hard B1 && + test_commit D1 && + test_merge E1 C1 && + test_commit F1 ' # Should result in: @@ -82,7 +38,7 @@ test_expect_success 'setup' ' # test_expect_success 'squash F1 into D1' ' FAKE_LINES="1 squash 3 2" git rebase -i -p B1 && - test "$(git rev-parse HEAD^2)" = "$(git rev-parse branch)" && + test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" && test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" && git tag E2 ' @@ -100,32 +56,15 @@ test_expect_success 'squash F1 into D1' ' # And rebase G1..M1 onto E2 test_expect_success 'rebase two levels of merge' ' - git checkout -b branch2 A1 && - touch g && - git add g && - git commit -m G1 && - git checkout -b branch3 && - touch h - git add h && - git commit -m H1 && - git checkout -b branch4 && - touch i && - git add i && - git commit -m I1 && - git tag I1 && - git checkout branch3 && - touch j && - git add j && - git commit -m J1 && - git merge I1 --no-commit && - git commit -m K1 && - git tag K1 && - git checkout branch2 && - touch l && - git add l && - git commit -m L1 && - git merge K1 --no-commit && - git commit -m M1 && + test_commit G1 && + test_commit H1 && + test_commit I1 && + git checkout -b branch3 H1 && + test_commit J1 && + test_merge K1 I1 && + git checkout -b branch2 G1 && + test_commit L1 && + test_merge M1 K1 && GIT_EDITOR=: git rebase -i -p E2 && test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" && test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" && diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 635958026..5869061c5 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -6,24 +6,20 @@ Tests if git rebase --root --onto <newparent> can rebase the root commit. ' . ./test-lib.sh +log_with_names () { + git rev-list --topo-order --parents --pretty="tformat:%s" HEAD | + git name-rev --stdin --name-only --refs=refs/heads/$1 +} + + test_expect_success 'prepare repository' ' - echo 1 > A && - git add A && - git commit -m 1 && - echo 2 > A && - git add A && - git commit -m 2 && + test_commit 1 A && + test_commit 2 A && git symbolic-ref HEAD refs/heads/other && rm .git/index && - echo 3 > B && - git add B && - git commit -m 3 && - echo 1 > A && - git add A && - git commit -m 1b && - echo 4 > B && - git add B && - git commit -m 4 + test_commit 3 B && + test_commit 1b A 1 && + test_commit 4 B ' test_expect_success 'rebase --root expects --onto' ' @@ -69,7 +65,7 @@ test_expect_success 'pre-rebase got correct input (2)' ' test_expect_success 'rebase -i --root --onto <newbase>' ' git checkout -b work3 other && - GIT_EDITOR=: git rebase -i --root --onto master && + git rebase -i --root --onto master && git log --pretty=tformat:"%s" > rebased3 && test_cmp expect rebased3 ' @@ -80,7 +76,7 @@ test_expect_success 'pre-rebase got correct input (3)' ' test_expect_success 'rebase -i --root --onto <newbase> <branch>' ' git branch work4 other && - GIT_EDITOR=: git rebase -i --root --onto master work4 && + git rebase -i --root --onto master work4 && git log --pretty=tformat:"%s" > rebased4 && test_cmp expect rebased4 ' @@ -91,7 +87,7 @@ test_expect_success 'pre-rebase got correct input (4)' ' test_expect_success 'rebase -i -p with linear history' ' git checkout -b work5 other && - GIT_EDITOR=: git rebase -i -p --root --onto master && + git rebase -i -p --root --onto master && git log --pretty=tformat:"%s" > rebased5 && test_cmp expect rebased5 ' @@ -103,28 +99,30 @@ test_expect_success 'pre-rebase got correct input (5)' ' test_expect_success 'set up merge history' ' git checkout other^ && git checkout -b side && - echo 5 > C && - git add C && - git commit -m 5 && + test_commit 5 C && git checkout other && git merge side ' -sed 's/#/ /g' > expect-side <<'EOF' -* Merge branch 'side' into other -|\## -| * 5 -* | 4 -|/## -* 3 -* 2 -* 1 +cat > expect-side <<'EOF' +commit work6 work6~1 work6^2 +Merge branch 'side' into other +commit work6^2 work6~2 +5 +commit work6~1 work6~2 +4 +commit work6~2 work6~3 +3 +commit work6~3 work6~4 +2 +commit work6~4 +1 EOF test_expect_success 'rebase -i -p with merge' ' git checkout -b work6 other && - GIT_EDITOR=: git rebase -i -p --root --onto master && - git log --graph --topo-order --pretty=tformat:"%s" > rebased6 && + git rebase -i -p --root --onto master && + log_with_names work6 > rebased6 && test_cmp expect-side rebased6 ' @@ -132,32 +130,34 @@ test_expect_success 'set up second root and merge' ' git symbolic-ref HEAD refs/heads/third && rm .git/index && rm A B C && - echo 6 > D && - git add D && - git commit -m 6 && + test_commit 6 D && git checkout other && git merge third ' -sed 's/#/ /g' > expect-third <<'EOF' -* Merge branch 'third' into other -|\## -| * 6 -* | Merge branch 'side' into other -|\ \## -| * | 5 -* | | 4 -|/ /## -* | 3 -|/## -* 2 -* 1 +cat > expect-third <<'EOF' +commit work7 work7~1 work7^2 +Merge branch 'third' into other +commit work7^2 work7~4 +6 +commit work7~1 work7~2 work7~1^2 +Merge branch 'side' into other +commit work7~1^2 work7~3 +5 +commit work7~2 work7~3 +4 +commit work7~3 work7~4 +3 +commit work7~4 work7~5 +2 +commit work7~5 +1 EOF test_expect_success 'rebase -i -p with two roots' ' git checkout -b work7 other && - GIT_EDITOR=: git rebase -i -p --root --onto master && - git log --graph --topo-order --pretty=tformat:"%s" > rebased7 && + git rebase -i -p --root --onto master && + log_with_names work7 > rebased7 && test_cmp expect-third rebased7 ' @@ -172,16 +172,109 @@ EOF test_expect_success 'pre-rebase hook stops rebase' ' git checkout -b stops1 other && - GIT_EDITOR=: test_must_fail git rebase --root --onto master && + test_must_fail git rebase --root --onto master && test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1 test 0 = $(git rev-list other...stops1 | wc -l) ' test_expect_success 'pre-rebase hook stops rebase -i' ' git checkout -b stops2 other && - GIT_EDITOR=: test_must_fail git rebase --root --onto master && + test_must_fail git rebase --root --onto master && test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2 test 0 = $(git rev-list other...stops2 | wc -l) ' +test_expect_success 'remove pre-rebase hook' ' + rm -f .git/hooks/pre-rebase +' + +test_expect_success 'set up a conflict' ' + git checkout master && + echo conflict > B && + git add B && + git commit -m conflict +' + +test_expect_success 'rebase --root with conflict (first part)' ' + git checkout -b conflict1 other && + test_must_fail git rebase --root --onto master && + git ls-files -u | grep "B$" +' + +test_expect_success 'fix the conflict' ' + echo 3 > B && + git add B +' + +cat > expect-conflict <<EOF +6 +5 +4 +3 +conflict +2 +1 +EOF + +test_expect_success 'rebase --root with conflict (second part)' ' + git rebase --continue && + git log --pretty=tformat:"%s" > conflict1 && + test_cmp expect-conflict conflict1 +' + +test_expect_success 'rebase -i --root with conflict (first part)' ' + git checkout -b conflict2 other && + test_must_fail git rebase -i --root --onto master && + git ls-files -u | grep "B$" +' + +test_expect_success 'fix the conflict' ' + echo 3 > B && + git add B +' + +test_expect_success 'rebase -i --root with conflict (second part)' ' + git rebase --continue && + git log --pretty=tformat:"%s" > conflict2 && + test_cmp expect-conflict conflict2 +' + +cat >expect-conflict-p <<\EOF +commit conflict3 conflict3~1 conflict3^2 +Merge branch 'third' into other +commit conflict3^2 conflict3~4 +6 +commit conflict3~1 conflict3~2 conflict3~1^2 +Merge branch 'side' into other +commit conflict3~1^2 conflict3~3 +5 +commit conflict3~2 conflict3~3 +4 +commit conflict3~3 conflict3~4 +3 +commit conflict3~4 conflict3~5 +conflict +commit conflict3~5 conflict3~6 +2 +commit conflict3~6 +1 +EOF + +test_expect_success 'rebase -i -p --root with conflict (first part)' ' + git checkout -b conflict3 other && + test_must_fail git rebase -i -p --root --onto master && + git ls-files -u | grep "B$" +' + +test_expect_success 'fix the conflict' ' + echo 3 > B && + git add B +' + +test_expect_success 'rebase -i -p --root with conflict (second part)' ' + git rebase --continue && + log_with_names conflict3 >out && + test_cmp expect-conflict-p out +' + test_done diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index 02efecae3..9055c8b31 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -82,4 +82,11 @@ test_expect_success \ git diff-index -M -p $tree > current && compare_diff_patch current expected' +test_expect_success \ + 'diff symlinks with non-existing targets' \ + 'ln -s narf pinky && + ln -s take\ over brain && + test_must_fail git diff --no-index pinky brain > output 2> output.err && + grep narf output && + ! grep error output.err' test_done diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh index 2f27a0ba9..a3f0897a5 100755 --- a/t/t4030-diff-textconv.sh +++ b/t/t4030-diff-textconv.sh @@ -104,7 +104,7 @@ cat >expect.typechange <<'EOF' -1 diff --git a/file b/file new file mode 120000 -index ad8b3d2..67be421 +index 0000000..67be421 --- /dev/null +++ b/file @@ -0,0 +1 @@ diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh new file mode 100755 index 000000000..1eb14989d --- /dev/null +++ b/t/t4033-diff-patience.sh @@ -0,0 +1,168 @@ +#!/bin/sh + +test_description='patience diff algorithm' + +. ./test-lib.sh + +cat >file1 <<\EOF +#include <stdio.h> + +// Frobs foo heartily +int frobnitz(int foo) +{ + int i; + for(i = 0; i < 10; i++) + { + printf("Your answer is: "); + printf("%d\n", foo); + } +} + +int fact(int n) +{ + if(n > 1) + { + return fact(n-1) * n; + } + return 1; +} + +int main(int argc, char **argv) +{ + frobnitz(fact(10)); +} +EOF + +cat >file2 <<\EOF +#include <stdio.h> + +int fib(int n) +{ + if(n > 2) + { + return fib(n-1) + fib(n-2); + } + return 1; +} + +// Frobs foo heartily +int frobnitz(int foo) +{ + int i; + for(i = 0; i < 10; i++) + { + printf("%d\n", foo); + } +} + +int main(int argc, char **argv) +{ + frobnitz(fib(10)); +} +EOF + +cat >expect <<\EOF +diff --git a/file1 b/file2 +index 6faa5a3..e3af329 100644 +--- a/file1 ++++ b/file2 +@@ -1,26 +1,25 @@ + #include <stdio.h> + ++int fib(int n) ++{ ++ if(n > 2) ++ { ++ return fib(n-1) + fib(n-2); ++ } ++ return 1; ++} ++ + // Frobs foo heartily + int frobnitz(int foo) + { + int i; + for(i = 0; i < 10; i++) + { +- printf("Your answer is: "); + printf("%d\n", foo); + } + } + +-int fact(int n) +-{ +- if(n > 1) +- { +- return fact(n-1) * n; +- } +- return 1; +-} +- + int main(int argc, char **argv) + { +- frobnitz(fact(10)); ++ frobnitz(fib(10)); + } +EOF + +test_expect_success 'patience diff' ' + + test_must_fail git diff --no-index --patience file1 file2 > output && + test_cmp expect output + +' + +test_expect_success 'patience diff output is valid' ' + + mv file2 expect && + git apply < output && + test_cmp expect file2 + +' + +cat >uniq1 <<\EOF +1 +2 +3 +4 +5 +6 +EOF + +cat >uniq2 <<\EOF +a +b +c +d +e +f +EOF + +cat >expect <<\EOF +diff --git a/uniq1 b/uniq2 +index b414108..0fdf397 100644 +--- a/uniq1 ++++ b/uniq2 +@@ -1,6 +1,6 @@ +-1 +-2 +-3 +-4 +-5 +-6 ++a ++b ++c ++d ++e ++f +EOF + +test_expect_success 'completely different files' ' + + test_must_fail git diff --no-index --patience uniq1 uniq2 > output && + test_cmp expect output + +' + +test_done diff --git a/t/t4034-diff-words.sh b/t/t4034-diff-words.sh new file mode 100755 index 000000000..4508effca --- /dev/null +++ b/t/t4034-diff-words.sh @@ -0,0 +1,200 @@ +#!/bin/sh + +test_description='word diff colors' + +. ./test-lib.sh + +test_expect_success setup ' + + git config diff.color.old red + git config diff.color.new green + +' + +decrypt_color () { + sed \ + -e 's/.\[1m/<WHITE>/g' \ + -e 's/.\[31m/<RED>/g' \ + -e 's/.\[32m/<GREEN>/g' \ + -e 's/.\[36m/<BROWN>/g' \ + -e 's/.\[m/<RESET>/g' +} + +word_diff () { + test_must_fail git diff --no-index "$@" pre post > output && + decrypt_color < output > output.decrypted && + test_cmp expect output.decrypted +} + +cat > pre <<\EOF +h(4) + +a = b + c +EOF + +cat > post <<\EOF +h(4),hh[44] + +a = b + c + +aa = a + +aeff = aeff * ( aaa ) +EOF + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<BROWN>@@ -1,3 +1,7 @@<RESET> +<RED>h(4)<RESET><GREEN>h(4),hh[44]<RESET> +<RESET> +a = b + c<RESET> + +<GREEN>aa = a<RESET> + +<GREEN>aeff = aeff * ( aaa )<RESET> +EOF + +test_expect_success 'word diff with runs of whitespace' ' + + word_diff --color-words + +' + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<BROWN>@@ -1,3 +1,7 @@<RESET> +h(4),<GREEN>hh<RESET>[44] +<RESET> +a = b + c<RESET> + +<GREEN>aa = a<RESET> + +<GREEN>aeff = aeff * ( aaa<RESET> ) +EOF +cp expect expect.letter-runs-are-words + +test_expect_success 'word diff with a regular expression' ' + + word_diff --color-words="[a-z]+" + +' + +test_expect_success 'set a diff driver' ' + git config diff.testdriver.wordRegex "[^[:space:]]" && + cat <<EOF > .gitattributes +pre diff=testdriver +post diff=testdriver +EOF +' + +test_expect_success 'option overrides .gitattributes' ' + + word_diff --color-words="[a-z]+" + +' + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<BROWN>@@ -1,3 +1,7 @@<RESET> +h(4)<GREEN>,hh[44]<RESET> +<RESET> +a = b + c<RESET> + +<GREEN>aa = a<RESET> + +<GREEN>aeff = aeff * ( aaa )<RESET> +EOF +cp expect expect.non-whitespace-is-word + +test_expect_success 'use regex supplied by driver' ' + + word_diff --color-words + +' + +test_expect_success 'set diff.wordRegex option' ' + git config diff.wordRegex "[[:alnum:]]+" +' + +cp expect.letter-runs-are-words expect + +test_expect_success 'command-line overrides config' ' + word_diff --color-words="[a-z]+" +' + +cp expect.non-whitespace-is-word expect + +test_expect_success '.gitattributes override config' ' + word_diff --color-words +' + +test_expect_success 'remove diff driver regex' ' + git config --unset diff.testdriver.wordRegex +' + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 330b04f..5ed8eff 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<BROWN>@@ -1,3 +1,7 @@<RESET> +h(4),<GREEN>hh[44<RESET>] +<RESET> +a = b + c<RESET> + +<GREEN>aa = a<RESET> + +<GREEN>aeff = aeff * ( aaa<RESET> ) +EOF + +test_expect_success 'use configured regex' ' + word_diff --color-words +' + +echo 'aaa (aaa)' > pre +echo 'aaa (aaa) aaa' > post + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index c29453b..be22f37 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<BROWN>@@ -1 +1 @@<RESET> +aaa (aaa) <GREEN>aaa<RESET> +EOF + +test_expect_success 'test parsing words for newline' ' + + word_diff --color-words="a+" + + +' + +echo '(:' > pre +echo '(' > post + +cat > expect <<\EOF +<WHITE>diff --git a/pre b/post<RESET> +<WHITE>index 289cb9d..2d06f37 100644<RESET> +<WHITE>--- a/pre<RESET> +<WHITE>+++ b/post<RESET> +<BROWN>@@ -1 +1 @@<RESET> +(<RED>:<RESET> +EOF + +test_expect_success 'test when words are only removed at the end' ' + + word_diff --color-words=. + +' + +test_done diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh index 55334927a..0f185caa4 100755 --- a/t/t4114-apply-typechange.sh +++ b/t/t4114-apply-typechange.sh @@ -25,6 +25,10 @@ test_expect_success 'setup repository and commits' ' git update-index foo && git commit -m "foo back to file" && git branch foo-back-to-file && + printf "\0" > foo && + git update-index foo && + git commit -m "foo becomes binary" && + git branch foo-becomes-binary && rm -f foo && git update-index --remove foo && mkdir foo && @@ -85,6 +89,20 @@ test_expect_success 'symlink becomes file' ' ' test_debug 'cat patch' +test_expect_success 'binary file becomes symlink' ' + git checkout -f foo-becomes-binary && + git diff-tree -p --binary HEAD foo-symlinked-to-bar > patch && + git apply --index < patch + ' +test_debug 'cat patch' + +test_expect_success 'symlink becomes binary file' ' + git checkout -f foo-symlinked-to-bar && + git diff-tree -p --binary HEAD foo-becomes-binary > patch && + git apply --index < patch + ' +test_debug 'cat patch' + test_expect_success 'symlink becomes directory' ' git checkout -f foo-symlinked-to-bar && diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 796f79526..5e65afa0c 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -257,4 +257,37 @@ test_expect_success 'am works from file (absolute path given) in subdirectory' ' test -z "$(git diff second)" ' +test_expect_success 'am --committer-date-is-author-date' ' + git checkout first && + test_tick && + git am --committer-date-is-author-date patch1 && + git cat-file commit HEAD | sed -e "/^$/q" >head1 && + at=$(sed -ne "/^author /s/.*> //p" head1) && + ct=$(sed -ne "/^committer /s/.*> //p" head1) && + test "$at" = "$ct" +' + +test_expect_success 'am without --committer-date-is-author-date' ' + git checkout first && + test_tick && + git am patch1 && + git cat-file commit HEAD | sed -e "/^$/q" >head1 && + at=$(sed -ne "/^author /s/.*> //p" head1) && + ct=$(sed -ne "/^committer /s/.*> //p" head1) && + test "$at" != "$ct" +' + +# This checks for +0000 because TZ is set to UTC and that should +# show up when the current time is used. The date in message is set +# by test_tick that uses -0700 timezone; if this feature does not +# work, we will see that instead of +0000. +test_expect_success 'am --ignore-date' ' + git checkout first && + test_tick && + git am --ignore-date patch1 && + git cat-file commit HEAD | sed -e "/^$/q" >head1 && + at=$(sed -ne "/^author /s/.*> //p" head1) && + echo "$at" | grep "+0000" +' + test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index fe1458942..e70ea94a1 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -11,7 +11,7 @@ test_expect_success 'split sample box' \ 'git mailsplit -o. "$TEST_DIRECTORY"/t5100/sample.mbox >last && last=`cat last` && echo total is $last && - test `cat last` = 11' + test `cat last` = 13' for mail in `echo 00*` do @@ -26,6 +26,28 @@ do ' done + +test_expect_success 'split box with rfc2047 samples' \ + 'mkdir rfc2047 && + git mailsplit -orfc2047 "$TEST_DIRECTORY"/t5100/rfc2047-samples.mbox \ + >rfc2047/last && + last=`cat rfc2047/last` && + echo total is $last && + test `cat rfc2047/last` = 11' + +for mail in `echo rfc2047/00*` +do + test_expect_success "mailinfo $mail" ' + git mailinfo -u $mail-msg $mail-patch <$mail >$mail-info && + echo msg && + test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-msg && + echo patch && + test_cmp "$TEST_DIRECTORY"/t5100/empty $mail-patch && + echo info && + test_cmp "$TEST_DIRECTORY"/t5100/rfc2047-info-$(basename $mail) $mail-info + ' +done + test_expect_success 'respect NULs' ' git mailsplit -d3 -o. "$TEST_DIRECTORY"/t5100/nul-plain && diff --git a/t/t5100/empty b/t/t5100/empty new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/t/t5100/empty diff --git a/t/t5100/info0001 b/t/t5100/info0001 index 8c052777e..f951538ac 100644 --- a/t/t5100/info0001 +++ b/t/t5100/info0001 @@ -1,4 +1,4 @@ -Author: A U Thor +Author: A (zzz) U Thor (Comment) Email: a.u.thor@example.com Subject: a commit. Date: Fri, 9 Jun 2006 00:44:16 -0700 diff --git a/t/t5100/info0012 b/t/t5100/info0012 new file mode 100644 index 000000000..ac1216ff7 --- /dev/null +++ b/t/t5100/info0012 @@ -0,0 +1,5 @@ +Author: Dmitriy Blinov +Email: bda@mnsspb.ru +Subject: Изменён список пакетов необходимых для сборки +Date: Wed, 12 Nov 2008 17:54:41 +0300 + diff --git a/t/t5100/info0013 b/t/t5100/info0013 new file mode 100644 index 000000000..bbe049e20 --- /dev/null +++ b/t/t5100/info0013 @@ -0,0 +1,5 @@ +Author: A U Thor +Email: a.u.thor@example.com +Subject: a patch +Date: Fri, 9 Jun 2006 00:44:16 -0700 + diff --git a/t/t5100/msg0012 b/t/t5100/msg0012 new file mode 100644 index 000000000..1dc2bf7f7 --- /dev/null +++ b/t/t5100/msg0012 @@ -0,0 +1,7 @@ +textlive-* исправлены на texlive-* +docutils заменён на python-docutils + +Действительно, оказалось, что rest2web вытягивает за собой +python-docutils. В то время как сам rest2web не нужен. + +Signed-off-by: Dmitriy Blinov <bda@mnsspb.ru> diff --git a/t/t5100/msg0013 b/t/t5100/msg0013 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/t/t5100/msg0013 diff --git a/t/t5100/patch0012 b/t/t5100/patch0012 new file mode 100644 index 000000000..36a0b6816 --- /dev/null +++ b/t/t5100/patch0012 @@ -0,0 +1,30 @@ +--- + howto/build_navy.txt | 6 +++--- + 1 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/howto/build_navy.txt b/howto/build_navy.txt +index 3fd3afb..0ee807e 100644 +--- a/howto/build_navy.txt ++++ b/howto/build_navy.txt +@@ -119,8 +119,8 @@ + - libxv-dev + - libusplash-dev + - latex-make +- - textlive-lang-cyrillic +- - textlive-latex-extra ++ - texlive-lang-cyrillic ++ - texlive-latex-extra + - dia + - python-pyrex + - libtool +@@ -128,7 +128,7 @@ + - sox + - cython + - imagemagick +- - docutils ++ - python-docutils + + #. на машине dinar: добавить свой открытый ssh-ключ в authorized_keys2 пользователя ddev + #. на своей машине: отредактировать /etc/sudoers (команда ``visudo``) примерно следующим образом:: +-- +1.5.6.5 diff --git a/t/t5100/patch0013 b/t/t5100/patch0013 new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/t/t5100/patch0013 diff --git a/t/t5100/rfc2047-info-0001 b/t/t5100/rfc2047-info-0001 new file mode 100644 index 000000000..0a383b07e --- /dev/null +++ b/t/t5100/rfc2047-info-0001 @@ -0,0 +1,4 @@ +Author: Keith Moore +Email: moore@cs.utk.edu +Subject: If you can read this you understand the example. + diff --git a/t/t5100/rfc2047-info-0002 b/t/t5100/rfc2047-info-0002 new file mode 100644 index 000000000..881be75d6 --- /dev/null +++ b/t/t5100/rfc2047-info-0002 @@ -0,0 +1,4 @@ +Author: Olle Järnefors +Email: ojarnef@admin.kth.se +Subject: Time for ISO 10646? + diff --git a/t/t5100/rfc2047-info-0003 b/t/t5100/rfc2047-info-0003 new file mode 100644 index 000000000..d0f789177 --- /dev/null +++ b/t/t5100/rfc2047-info-0003 @@ -0,0 +1,4 @@ +Author: Patrik Fältström +Email: paf@nada.kth.se +Subject: RFC-HDR care and feeding + diff --git a/t/t5100/rfc2047-info-0004 b/t/t5100/rfc2047-info-0004 new file mode 100644 index 000000000..f67a90a97 --- /dev/null +++ b/t/t5100/rfc2047-info-0004 @@ -0,0 +1,4 @@ +Author: Nathaniel Borenstein (םולש ןב ילטפנ) +Email: nsb@thumper.bellcore.com +Subject: Test of new header generator + diff --git a/t/t5100/rfc2047-info-0005 b/t/t5100/rfc2047-info-0005 new file mode 100644 index 000000000..c27be3bf2 --- /dev/null +++ b/t/t5100/rfc2047-info-0005 @@ -0,0 +1,2 @@ +Subject: (a) + diff --git a/t/t5100/rfc2047-info-0006 b/t/t5100/rfc2047-info-0006 new file mode 100644 index 000000000..9dad47445 --- /dev/null +++ b/t/t5100/rfc2047-info-0006 @@ -0,0 +1,2 @@ +Subject: (a b) + diff --git a/t/t5100/rfc2047-info-0007 b/t/t5100/rfc2047-info-0007 new file mode 100644 index 000000000..294f195a5 --- /dev/null +++ b/t/t5100/rfc2047-info-0007 @@ -0,0 +1,2 @@ +Subject: (ab) + diff --git a/t/t5100/rfc2047-info-0008 b/t/t5100/rfc2047-info-0008 new file mode 100644 index 000000000..294f195a5 --- /dev/null +++ b/t/t5100/rfc2047-info-0008 @@ -0,0 +1,2 @@ +Subject: (ab) + diff --git a/t/t5100/rfc2047-info-0009 b/t/t5100/rfc2047-info-0009 new file mode 100644 index 000000000..294f195a5 --- /dev/null +++ b/t/t5100/rfc2047-info-0009 @@ -0,0 +1,2 @@ +Subject: (ab) + diff --git a/t/t5100/rfc2047-info-0010 b/t/t5100/rfc2047-info-0010 new file mode 100644 index 000000000..9dad47445 --- /dev/null +++ b/t/t5100/rfc2047-info-0010 @@ -0,0 +1,2 @@ +Subject: (a b) + diff --git a/t/t5100/rfc2047-info-0011 b/t/t5100/rfc2047-info-0011 new file mode 100644 index 000000000..9dad47445 --- /dev/null +++ b/t/t5100/rfc2047-info-0011 @@ -0,0 +1,2 @@ +Subject: (a b) + diff --git a/t/t5100/rfc2047-samples.mbox b/t/t5100/rfc2047-samples.mbox new file mode 100644 index 000000000..3ca2470da --- /dev/null +++ b/t/t5100/rfc2047-samples.mbox @@ -0,0 +1,48 @@ +From nobody Mon Sep 17 00:00:00 2001 +From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu> +To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk> +CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be> +Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= + =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= + +From nobody Mon Sep 17 00:00:00 2001 +From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se> +To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se +Subject: Time for ISO 10646? + +From nobody Mon Sep 17 00:00:00 2001 +To: Dave Crocker <dcrocker@mordor.stanford.edu> +Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se +From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se> +Subject: Re: RFC-HDR care and feeding + +From nobody Mon Sep 17 00:00:00 2001 +From: Nathaniel Borenstein <nsb@thumper.bellcore.com> + (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) +To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed + <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu> +Subject: Test of new header generator +MIME-Version: 1.0 +Content-type: text/plain; charset=ISO-8859-1 + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a?=) + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a?= b) + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a?= + =?ISO-8859-1?Q?b?=) + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a_b?=) + +From nobody Mon Sep 17 00:00:00 2001 +Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=) diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 4bf7947b4..c5ad206b4 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -2,7 +2,10 @@ From nobody Mon Sep 17 00:00:00 2001 -From: A U Thor <a.u.thor@example.com> +From: A (zzz) + U + Thor + <a.u.thor@example.com> (Comment) Date: Fri, 9 Jun 2006 00:44:16 -0700 Subject: [PATCH] a commit. @@ -501,3 +504,60 @@ index 3e5fe51..aabfe5c 100644 --=-=-=-- +From bda@mnsspb.ru Wed Nov 12 17:54:41 2008 +From: Dmitriy Blinov <bda@mnsspb.ru> +To: navy-patches@dinar.mns.mnsspb.ru +Date: Wed, 12 Nov 2008 17:54:41 +0300 +Message-Id: <1226501681-24923-1-git-send-email-bda@mnsspb.ru> +X-Mailer: git-send-email 1.5.6.5 +MIME-Version: 1.0 +Content-Type: text/plain; + charset=utf-8 +Content-Transfer-Encoding: 8bit +Subject: [Navy-patches] [PATCH] + =?utf-8?b?0JjQt9C80LXQvdGR0L0g0YHQv9C40YHQvtC6INC/0LA=?= + =?utf-8?b?0LrQtdGC0L7QsiDQvdC10L7QsdGF0L7QtNC40LzRi9GFINC00LvRjyA=?= + =?utf-8?b?0YHQsdC+0YDQutC4?= + +textlive-* исправлены на texlive-* +docutils заменён на python-docutils + +Действительно, оказалось, что rest2web вытягивает за собой +python-docutils. В то время как сам rest2web не нужен. + +Signed-off-by: Dmitriy Blinov <bda@mnsspb.ru> +--- + howto/build_navy.txt | 6 +++--- + 1 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/howto/build_navy.txt b/howto/build_navy.txt +index 3fd3afb..0ee807e 100644 +--- a/howto/build_navy.txt ++++ b/howto/build_navy.txt +@@ -119,8 +119,8 @@ + - libxv-dev + - libusplash-dev + - latex-make +- - textlive-lang-cyrillic +- - textlive-latex-extra ++ - texlive-lang-cyrillic ++ - texlive-latex-extra + - dia + - python-pyrex + - libtool +@@ -128,7 +128,7 @@ + - sox + - cython + - imagemagick +- - docutils ++ - python-docutils + + #. на машине dinar: добавить свой открытый ssh-ключ в authorized_keys2 пользователя ddev + #. на своей машине: отредактировать /etc/sudoers (команда ``visudo``) примерно следующим образом:: +-- +1.5.6.5 +From nobody Mon Sep 17 00:00:00 2001 +From: <a.u.thor@example.com> (A U Thor) +Date: Fri, 9 Jun 2006 00:44:16 -0700 +Subject: [PATCH] a patch + diff --git a/t/t5519-push-alternates.sh b/t/t5519-push-alternates.sh new file mode 100755 index 000000000..96be5236a --- /dev/null +++ b/t/t5519-push-alternates.sh @@ -0,0 +1,143 @@ +#!/bin/sh + +test_description='push to a repository that borrows from elsewhere' + +. ./test-lib.sh + +test_expect_success setup ' + mkdir alice-pub && + ( + cd alice-pub && + GIT_DIR=. git init + ) && + mkdir alice-work && + ( + cd alice-work && + git init && + >file && + git add . && + git commit -m initial && + git push ../alice-pub master + ) && + + # Project Bob is a fork of project Alice + mkdir bob-pub && + ( + cd bob-pub && + GIT_DIR=. git init && + mkdir -p objects/info && + echo ../../alice-pub/objects >objects/info/alternates + ) && + git clone alice-pub bob-work && + ( + cd bob-work && + git push ../bob-pub master + ) +' + +test_expect_success 'alice works and pushes' ' + ( + cd alice-work && + echo more >file && + git commit -a -m second && + git push ../alice-pub + ) +' + +test_expect_success 'bob fetches from alice, works and pushes' ' + ( + # Bob acquires what Alice did in his work tree first. + # Even though these objects are not directly in + # the public repository of Bob, this push does not + # need to send the commit Bob received from Alice + # to his public repository, as all the object Alice + # has at her public repository are available to it + # via its alternates. + cd bob-work && + git pull ../alice-pub master && + echo more bob >file && + git commit -a -m third && + git push ../bob-pub + ) && + + # Check that the second commit by Alice is not sent + # to ../bob-pub + ( + cd bob-pub && + second=$(git rev-parse HEAD^) && + rm -f objects/info/alternates && + test_must_fail git cat-file -t $second && + echo ../../alice-pub/objects >objects/info/alternates + ) +' + +test_expect_success 'clean-up in case the previous failed' ' + ( + cd bob-pub && + echo ../../alice-pub/objects >objects/info/alternates + ) +' + +test_expect_success 'alice works and pushes again' ' + ( + # Alice does not care what Bob does. She does not + # even have to be aware of his existence. She just + # keeps working and pushing + cd alice-work && + echo more alice >file && + git commit -a -m fourth && + git push ../alice-pub + ) +' + +test_expect_success 'bob works and pushes' ' + ( + # This time Bob does not pull from Alice, and + # the master branch at her public repository points + # at a commit Bob does not know about. This should + # not prevent the push by Bob from succeeding. + cd bob-work && + echo yet more bob >file && + git commit -a -m fifth && + git push ../bob-pub + ) +' + +test_expect_success 'alice works and pushes yet again' ' + ( + # Alice does not care what Bob does. She does not + # even have to be aware of his existence. She just + # keeps working and pushing + cd alice-work && + echo more and more alice >file && + git commit -a -m sixth.1 && + echo more and more alice >>file && + git commit -a -m sixth.2 && + echo more and more alice >>file && + git commit -a -m sixth.3 && + git push ../alice-pub + ) +' + +test_expect_success 'bob works and pushes again' ' + ( + cd alice-pub && + git cat-file commit master >../bob-work/commit + ) + ( + # This time Bob does not pull from Alice, and + # the master branch at her public repository points + # at a commit Bob does not fully know about, but + # he happens to have the commit object (but not the + # necessary tree) in his repository from Alice. + # This should not prevent the push by Bob from + # succeeding. + cd bob-work && + git hash-object -t commit -w commit && + echo even more bob >file && + git commit -a -m seventh && + git push ../bob-pub + ) +' + +test_done diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index 8dfaaa456..3559d1796 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -11,8 +11,8 @@ test_expect_success 'preparing origin repository' ' git clone --bare . x && test "$(GIT_CONFIG=a.git/config git config --bool core.bare)" = true && test "$(GIT_CONFIG=x/config git config --bool core.bare)" = true - git bundle create b1.bundle --all HEAD && - git bundle create b2.bundle --all && + git bundle create b1.bundle --all && + git bundle create b2.bundle master && mkdir dir && cp b1.bundle dir/b3 cp b1.bundle b4 @@ -116,4 +116,20 @@ test_expect_success 'bundle clone with nonexistent HEAD' ' test ! -e .git/refs/heads/master ' +test_expect_success 'clone empty repository' ' + cd "$D" && + mkdir empty && + (cd empty && git init) && + git clone empty empty-clone && + test_tick && + (cd empty-clone + echo "content" >> foo && + git add foo && + git commit -m "Initial commit" && + git push origin master && + expected=$(git rev-parse master) && + actual=$(git --git-dir=../empty/.git rev-parse master) && + test $actual = $expected) +' + test_done diff --git a/t/t6014-rev-list-all.sh b/t/t6014-rev-list-all.sh new file mode 100755 index 000000000..991ab4a65 --- /dev/null +++ b/t/t6014-rev-list-all.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='--all includes detached HEADs' + +. ./test-lib.sh + + +commit () { + test_tick && + echo $1 > foo && + git add foo && + git commit -m "$1" +} + +test_expect_success 'setup' ' + + commit one && + commit two && + git checkout HEAD^ && + commit detached + +' + +test_expect_success 'rev-list --all lists detached HEAD' ' + + test 3 = $(git rev-list --all | wc -l) + +' + +test_expect_success 'repack does not lose detached HEAD' ' + + git gc && + git prune --expire=now && + git show HEAD + +' + +test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index ef2e78f9d..8fb3a5683 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -55,9 +55,17 @@ test_expect_success \ git mv -k untracked1 untracked2 path0 && test -f untracked1 && test -f untracked2 && - test ! -f path0/untracked1 + test ! -f path0/untracked1 && test ! -f path0/untracked2' +test_expect_success \ + 'checking -f on untracked file with existing target' \ + 'touch path0/untracked1 && + git mv -f untracked1 path0 + test ! -f .git/index.lock && + test -f untracked1 && + test -f path0/untracked1' + # clean up the mess in case bad things happen rm -f idontexist untracked1 untracked2 \ path0/idontexist path0/untracked1 path0/untracked2 \ diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index f377fea4b..69501e271 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1090,6 +1090,121 @@ test_expect_success 'filename for the message is relative to cwd' ' git cat-file tag tag-from-subdir-2 | grep "in sub directory" ' +# create a few more commits to test --contains + +hash1=$(git rev-parse HEAD) + +test_expect_success 'creating second commit and tag' ' + echo foo-2.0 >foo && + git add foo && + git commit -m second + git tag v2.0 +' + +hash2=$(git rev-parse HEAD) + +test_expect_success 'creating third commit without tag' ' + echo foo-dev >foo && + git add foo && + git commit -m third +' + +hash3=$(git rev-parse HEAD) + +# simple linear checks of --continue + +cat > expected <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +v2.0 +EOF + +test_expect_success 'checking that first commit is in all tags (hash)' " + git tag -l --contains $hash1 v* >actual + test_cmp expected actual +" + +# other ways of specifying the commit +test_expect_success 'checking that first commit is in all tags (tag)' " + git tag -l --contains v1.0 v* >actual + test_cmp expected actual +" + +test_expect_success 'checking that first commit is in all tags (relative)' " + git tag -l --contains HEAD~2 v* >actual + test_cmp expected actual +" + +cat > expected <<EOF +v2.0 +EOF + +test_expect_success 'checking that second commit only has one tag' " + git tag -l --contains $hash2 v* >actual + test_cmp expected actual +" + + +cat > expected <<EOF +EOF + +test_expect_success 'checking that third commit has no tags' " + git tag -l --contains $hash3 v* >actual + test_cmp expected actual +" + +# how about a simple merge? + +test_expect_success 'creating simple branch' ' + git branch stable v2.0 && + git checkout stable && + echo foo-3.0 > foo && + git commit foo -m fourth + git tag v3.0 +' + +hash4=$(git rev-parse HEAD) + +cat > expected <<EOF +v3.0 +EOF + +test_expect_success 'checking that branch head only has one tag' " + git tag -l --contains $hash4 v* >actual + test_cmp expected actual +" + +test_expect_success 'merging original branch into this branch' ' + git merge --strategy=ours master && + git tag v4.0 +' + +cat > expected <<EOF +v4.0 +EOF + +test_expect_success 'checking that original branch head has one tag now' " + git tag -l --contains $hash3 v* >actual + test_cmp expected actual +" + +cat > expected <<EOF +v0.2.1 +v1.0 +v1.0.1 +v1.1.3 +v2.0 +v3.0 +v4.0 +EOF + +test_expect_success 'checking that initial commit is in all tags' " + git tag -l --contains $hash1 v* >actual + test_cmp expected actual +" + # mixing modes and options: test_expect_success 'mixing incompatibles modes and options is forbidden' ' diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 63a8225ae..5babdf26e 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -50,12 +50,10 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' compare_mtimes () { - perl -e 'my $reference = shift; - foreach my $file (@ARGV) { - exit(1) unless(-f $file && -M $file == -M $reference); - } - exit(0); - ' -- "$@" + read tref rest && + while read t rest; do + test "$tref" = "$t" || break + done } test_expect_success '-A without -d option leaves unreachable objects packed' ' @@ -87,7 +85,9 @@ test_expect_success 'unpacked objects receive timestamp of pack file' ' tmppack=".git/objects/pack/tmp_pack" && ln "$packfile" "$tmppack" && git repack -A -l -d && - compare_mtimes "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" + test-chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \ + > mtimes && + compare_mtimes < mtimes ' test_done diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh new file mode 100755 index 000000000..c4b5b8bcf --- /dev/null +++ b/t/t9134-git-svn-ignore-paths.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# +# Copyright (c) 2009 Vitaly Shukela +# Copyright (c) 2009 Eric Wong +# + +test_description='git svn property tests' +. ./lib-git-svn.sh + +test_expect_success 'setup test repository' ' + svn co "$svnrepo" s && + ( + cd s && + mkdir qqq www && + echo test_qqq > qqq/test_qqq.txt && + echo test_www > www/test_www.txt && + svn add qqq && + svn add www && + svn commit -m "create some files" && + svn up && + echo hi >> www/test_www.txt && + svn commit -m "modify www/test_www.txt" && + svn up + ) +' + +test_expect_success 'clone an SVN repository with ignored www directory' ' + git svn clone --ignore-paths="^www" "$svnrepo" g && + echo test_qqq > expect && + for i in g/*/*.txt; do cat $i >> expect2; done && + test_cmp expect expect2 +' + +test_expect_success 'SVN-side change outside of www' ' + ( + cd s && + echo b >> qqq/test_qqq.txt && + svn commit -m "SVN-side change outside of www" && + svn up && + svn log -v | fgrep "SVN-side change outside of www" + ) +' + +test_expect_success 'update git svn-cloned repo' ' + ( + cd g && + git svn rebase --ignore-paths="^www" && + printf "test_qqq\nb\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_expect_success 'SVN-side change inside of ignored www' ' + ( + cd s && + echo zaq >> www/test_www.txt + svn commit -m "SVN-side change inside of www/test_www.txt" && + svn up && + svn log -v | fgrep "SVN-side change inside of www/test_www.txt" + ) +' + +test_expect_success 'update git svn-cloned repo' ' + ( + cd g && + git svn rebase --ignore-paths="^www" && + printf "test_qqq\nb\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_expect_success 'SVN-side change in and out of ignored www' ' + ( + cd s && + echo cvf >> www/test_www.txt + echo ygg >> qqq/test_qqq.txt + svn commit -m "SVN-side change in and out of ignored www" && + svn up && + svn log -v | fgrep "SVN-side change in and out of ignored www" + ) +' + +test_expect_success 'update git svn-cloned repo again' ' + ( + cd g && + git svn rebase --ignore-paths="^www" && + printf "test_qqq\nb\nygg\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_done diff --git a/t/test-lib.sh b/t/test-lib.sh index 41d5a5996..6f6244ab7 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -193,6 +193,31 @@ test_tick () { export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } +# Call test_commit with the arguments "<message> [<file> [<contents>]]" +# +# This will commit a file with the given contents and the given commit +# message. It will also add a tag with <message> as name. +# +# Both <file> and <contents> default to <message>. + +test_commit () { + file=${2:-"$1.t"} + echo "${3-$1}" > "$file" && + git add "$file" && + test_tick && + git commit -m "$1" && + git tag "$1" +} + +# Call test_merge with the arguments "<message> <commit>", where <commit> +# can be a tag pointing to the commit-to-merge. + +test_merge () { + test_tick && + git merge -m "$1" "$2" && + git tag "$1" +} + # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. diff --git a/dump-cache-tree.c b/test-dump-cache-tree.c index 1f73f1ea7..1f73f1ea7 100644 --- a/dump-cache-tree.c +++ b/test-dump-cache-tree.c diff --git a/test-path-utils.c b/test-path-utils.c index a0bcb0e21..2c0f5a37e 100644 --- a/test-path-utils.c +++ b/test-path-utils.c @@ -3,7 +3,7 @@ int main(int argc, char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_absolute_path")) { - char *buf = xmalloc(strlen(argv[2])+1); + char *buf = xmalloc(PATH_MAX + 1); int rv = normalize_absolute_path(buf, argv[2]); assert(strlen(buf) == rv); puts(buf); diff --git a/test-sigchain.c b/test-sigchain.c new file mode 100644 index 000000000..42db234e8 --- /dev/null +++ b/test-sigchain.c @@ -0,0 +1,22 @@ +#include "sigchain.h" +#include "cache.h" + +#define X(f) \ +static void f(int sig) { \ + puts(#f); \ + fflush(stdout); \ + sigchain_pop(sig); \ + raise(sig); \ +} +X(one) +X(two) +X(three) +#undef X + +int main(int argc, char **argv) { + sigchain_push(SIGTERM, one); + sigchain_push(SIGTERM, two); + sigchain_push(SIGTERM, three); + raise(SIGTERM); + return 0; +} diff --git a/unpack-file.c b/unpack-file.c index bcdc8bbb3..75cd2f1a6 100644 --- a/unpack-file.c +++ b/unpack-file.c @@ -1,5 +1,6 @@ #include "cache.h" #include "blob.h" +#include "exec_cmd.h" static char *create_temp_file(unsigned char *sha1) { @@ -25,8 +26,10 @@ int main(int argc, char **argv) { unsigned char sha1[20]; + git_extract_argv0_path(argv[0]); + if (argc != 2) - usage("git-unpack-file <sha1>"); + usage("git unpack-file <sha1>"); if (get_sha1(argv[1], sha1)) die("Not a valid object name %s", argv[1]); diff --git a/unpack-trees.c b/unpack-trees.c index 15c9ef592..e547282ed 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -61,7 +61,7 @@ static void unlink_entry(struct cache_entry *ce) char *cp, *prev; char *name = ce->name; - if (has_symlink_leading_path(ce_namelen(ce), ce->name)) + if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name)) return; if (unlink(name)) return; @@ -240,8 +240,11 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con return ce; } -static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmask, struct cache_entry *src[5], - const struct name_entry *names, const struct traverse_info *info) +static int unpack_nondirectories(int n, unsigned long mask, + unsigned long dirmask, + struct cache_entry **src, + const struct name_entry *names, + const struct traverse_info *info) { int i; struct unpack_trees_options *o = info->data; @@ -291,7 +294,7 @@ static int unpack_nondirectories(int n, unsigned long mask, unsigned long dirmas static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info) { - struct cache_entry *src[5] = { NULL, }; + struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, }; struct unpack_trees_options *o = info->data; const struct name_entry *p = names; @@ -580,7 +583,7 @@ static int verify_absent(struct cache_entry *ce, const char *action, if (o->index_only || o->reset || !o->update) return 0; - if (has_symlink_leading_path(ce_namelen(ce), ce->name)) + if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name)) return 0; if (!lstat(ce->name, &st)) { diff --git a/update-server-info.c b/update-server-info.c index 7e8209ea4..7b38fd867 100644 --- a/update-server-info.c +++ b/update-server-info.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "exec_cmd.h" static const char update_server_info_usage[] = "git update-server-info [--force]"; @@ -19,6 +20,8 @@ int main(int ac, char **av) 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 e5adbc011..19c24db64 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -11,7 +11,7 @@ #include "list-objects.h" #include "run-command.h" -static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>"; +static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=nn] <dir>"; /* bits #0..7 in revision.h, #8..10 in commit.c */ #define THEY_HAVE (1u << 11) @@ -616,6 +616,8 @@ int main(int argc, char **argv) int i; int strict = 0; + git_extract_argv0_path(argv[0]); + for (i = 1; i < argc; i++) { char *arg = argv[i]; diff --git a/userdiff.c b/userdiff.c index 3681062eb..d556da975 100644 --- a/userdiff.c +++ b/userdiff.c @@ -6,14 +6,20 @@ static struct userdiff_driver *drivers; static int ndrivers; static int drivers_alloc; -#define FUNCNAME(name, pattern) \ - { name, NULL, -1, { pattern, REG_EXTENDED } } +#define PATTERNS(name, pattern, word_regex) \ + { name, NULL, -1, { pattern, REG_EXTENDED }, word_regex } static struct userdiff_driver builtin_drivers[] = { -FUNCNAME("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$"), -FUNCNAME("java", +PATTERNS("html", "^[ \t]*(<[Hh][1-6][ \t].*>.*)$", + "[^<>= \t]+|[^[:space:]]|[\x80-\xff]+"), +PATTERNS("java", "!^[ \t]*(catch|do|for|if|instanceof|new|return|switch|throw|while)\n" - "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$"), -FUNCNAME("objc", + "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$", + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=" + "|--|\\+\\+|<<=?|>>>?=?|&&|\\|\\|" + "|[^[:space:]]|[\x80-\xff]+"), +PATTERNS("objc", /* Negate C statements that can look like functions */ "!^[ \t]*(do|for|if|else|return|switch|while)\n" /* Objective-C methods */ @@ -21,20 +27,60 @@ FUNCNAME("objc", /* C functions */ "^[ \t]*(([ \t]*[A-Za-z_][A-Za-z_0-9]*){2,}[ \t]*\\([^;]*)$\n" /* Objective-C class/protocol definitions */ - "^(@(implementation|interface|protocol)[ \t].*)$"), -FUNCNAME("pascal", + "^(@(implementation|interface|protocol)[ \t].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" + "|[^[:space:]]|[\x80-\xff]+"), +PATTERNS("pascal", "^((procedure|function|constructor|destructor|interface|" "implementation|initialization|finalization)[ \t]*.*)$" "\n" - "^(.*=[ \t]*(class|record).*)$"), -FUNCNAME("php", "^[\t ]*((function|class).*)"), -FUNCNAME("python", "^[ \t]*((class|def)[ \t].*)$"), -FUNCNAME("ruby", "^[ \t]*((class|module|def)[ \t].*)$"), -FUNCNAME("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$"), -FUNCNAME("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$"), + "^(.*=[ \t]*(class|record).*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" + "|<>|<=|>=|:=|\\.\\." + "|[^[:space:]]|[\x80-\xff]+"), +PATTERNS("php", "^[\t ]*((function|class).*)", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+" + "|[-+*/<>%&^|=!.]=|--|\\+\\+|<<=?|>>=?|===|&&|\\|\\||::|->" + "|[^[:space:]]|[\x80-\xff]+"), +PATTERNS("python", "^[ \t]*((class|def)[ \t].*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[jJlL]?|0[xX]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|//=?|<<=?|>>=?|\\*\\*=?" + "|[^[:space:]|[\x80-\xff]+"), + /* -- */ +PATTERNS("ruby", "^[ \t]*((class|module|def)[ \t].*)$", + /* -- */ + "(@|@@|\\$)?[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+|0[xXbB]?[0-9a-fA-F]+|\\?(\\\\C-)?(\\\\M-)?." + "|//=?|[-+*/<>%&^|=!]=|<<=?|>>=?|===|\\.{1,3}|::|[!=]~" + "|[^[:space:]|[\x80-\xff]+"), +PATTERNS("bibtex", "(@[a-zA-Z]{1,}[ \t]*\\{{0,1}[ \t]*[^ \t\"@',\\#}{~%]*).*$", + "[={}\"]|[^={}\" \t]+"), +PATTERNS("tex", "^(\\\\((sub)*section|chapter|part)\\*{0,1}\\{.*)$", + "\\\\[a-zA-Z@]+|\\\\.|[a-zA-Z0-9\x80-\xff]+|[^[:space:]]"), +PATTERNS("cpp", + /* Jump targets or access declarations */ + "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n" + /* C/++ functions/methods at top level */ + "^([A-Za-z_][A-Za-z_0-9]*([ \t]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n" + /* compound type at top level */ + "^((struct|class|enum)[^;]*)$", + /* -- */ + "[a-zA-Z_][a-zA-Z0-9_]*" + "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?" + "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->" + "|[^[:space:]]|[\x80-\xff]+"), { "default", NULL, -1, { NULL, 0 } }, }; -#undef FUNCNAME +#undef PATTERNS static struct userdiff_driver driver_true = { "diff=true", @@ -134,6 +180,8 @@ int userdiff_config(const char *k, const char *v) return parse_string(&drv->external, k, v); if ((drv = parse_driver(k, v, "textconv"))) return parse_string(&drv->textconv, k, v); + if ((drv = parse_driver(k, v, "wordregex"))) + return parse_string(&drv->word_regex, k, v); return 0; } diff --git a/userdiff.h b/userdiff.h index ba2945770..c3151594f 100644 --- a/userdiff.h +++ b/userdiff.h @@ -11,6 +11,7 @@ struct userdiff_driver { const char *external; int binary; struct userdiff_funcname funcname; + const char *word_regex; const char *textconv; }; @@ -4,6 +4,7 @@ * Copyright (C) Eric Biederman, 2005 */ #include "cache.h" +#include "exec_cmd.h" static const char var_usage[] = "git var [-l | <variable>]"; @@ -56,6 +57,8 @@ int main(int argc, char **argv) usage(var_usage); } + git_extract_argv0_path(argv[0]); + setup_git_directory_gently(&nongit); val = NULL; diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 361f80231..4da052a3f 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -32,6 +32,7 @@ extern "C" { #define XDF_IGNORE_WHITESPACE (1 << 2) #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) +#define XDF_PATIENCE_DIFF (1 << 5) #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) #define XDL_PATCH_NORMAL '-' diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 9d0324a38..3e97462bd 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -329,6 +329,9 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdalgoenv_t xenv; diffdata_t dd1, dd2; + if (xpp->flags & XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { return -1; diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index 3e099dc44..ad033a8e6 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -55,5 +55,7 @@ int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); void xdl_free_script(xdchange_t *xscr); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg); +int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, + xdfenv_t *env); #endif /* #if !defined(XDIFFI_H) */ diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c new file mode 100644 index 000000000..e42c16a80 --- /dev/null +++ b/xdiff/xpatience.c @@ -0,0 +1,381 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2009 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ +#include "xinclude.h" +#include "xtypes.h" +#include "xdiff.h" + +/* + * The basic idea of patience diff is to find lines that are unique in + * both files. These are intuitively the ones that we want to see as + * common lines. + * + * The maximal ordered sequence of such line pairs (where ordered means + * that the order in the sequence agrees with the order of the lines in + * both files) naturally defines an initial set of common lines. + * + * Now, the algorithm tries to extend the set of common lines by growing + * the line ranges where the files have identical lines. + * + * Between those common lines, the patience diff algorithm is applied + * recursively, until no unique line pairs can be found; these line ranges + * are handled by the well-known Myers algorithm. + */ + +#define NON_UNIQUE ULONG_MAX + +/* + * This is a hash mapping from line hash to line numbers in the first and + * second file. + */ +struct hashmap { + int nr, alloc; + struct entry { + unsigned long hash; + /* + * 0 = unused entry, 1 = first line, 2 = second, etc. + * line2 is NON_UNIQUE if the line is not unique + * in either the first or the second file. + */ + unsigned long line1, line2; + /* + * "next" & "previous" are used for the longest common + * sequence; + * initially, "next" reflects only the order in file1. + */ + struct entry *next, *previous; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(int line, struct hashmap *map, int pass) +{ + xrecord_t **records = pass == 1 ? + map->env->xdf1.recs : map->env->xdf2.recs; + xrecord_t *record = records[line - 1], *other; + /* + * After xdl_prepare_env() (or more precisely, due to + * xdl_classify_record()), the "ha" member of the records (AKA lines) + * is _not_ the hash anymore, but a linearized version of it. In + * other words, the "ha" member is guaranteed to start with 0 and + * the second record's ha can only be 0 or 1, etc. + * + * So we multiply ha by 2 in the hope that the hashing was + * "unique enough". + */ + int index = (int)((record->ha << 1) % map->alloc); + + while (map->entries[index].line1) { + other = map->env->xdf1.recs[map->entries[index].line1 - 1]; + if (map->entries[index].hash != record->ha || + !xdl_recmatch(record->ptr, record->size, + other->ptr, other->size, + map->xpp->flags)) { + if (++index >= map->alloc) + index = 0; + continue; + } + if (pass == 2) + map->has_matches = 1; + if (pass == 1 || map->entries[index].line2) + map->entries[index].line2 = NON_UNIQUE; + else + map->entries[index].line2 = line; + return; + } + if (pass == 2) + return; + map->entries[index].line1 = line; + map->entries[index].hash = record->ha; + if (!map->first) + map->first = map->entries + index; + if (map->last) { + map->last->next = map->entries + index; + map->entries[index].previous = map->last; + } + map->last = map->entries + index; + map->nr++; +} + +/* + * This function has to be called for each recursion into the inter-hunk + * parts, as previously non-unique lines can become unique when being + * restricted to a smaller part of the files. + * + * It is assumed that env has been prepared using xdl_prepare(). + */ +static int fill_hashmap(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + struct hashmap *result, + int line1, int count1, int line2, int count2) +{ + result->file1 = file1; + result->file2 = file2; + result->xpp = xpp; + result->env = env; + + /* We know exactly how large we want the hash map */ + result->alloc = count1 * 2; + result->entries = (struct entry *) + xdl_malloc(result->alloc * sizeof(struct entry)); + if (!result->entries) + return -1; + memset(result->entries, 0, result->alloc * sizeof(struct entry)); + + /* First, fill with entries from the first file */ + while (count1--) + insert_record(line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(line2++, result, 2); + + return 0; +} + +/* + * Find the longest sequence with a smaller last element (meaning a smaller + * line2, as we construct the sequence with entries ordered by line1). + */ +static int binary_search(struct entry **sequence, int longest, + struct entry *entry) +{ + int left = -1, right = longest; + + while (left + 1 < right) { + int middle = (left + right) / 2; + /* by construction, no two entries can be equal */ + if (sequence[middle]->line2 > entry->line2) + right = middle; + else + left = middle; + } + /* return the index in "sequence", _not_ the sequence length */ + return left; +} + +/* + * The idea is to start with the list of common unique lines sorted by + * the order in file1. For each of these pairs, the longest (partial) + * sequence whose last element's line2 is smaller is determined. + * + * For efficiency, the sequences are kept in a list containing exactly one + * item per sequence length: the sequence with the smallest last + * element (in terms of line2). + */ +static struct entry *find_longest_common_sequence(struct hashmap *map) +{ + struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *)); + int longest = 0, i; + struct entry *entry; + + for (entry = map->first; entry; entry = entry->next) { + if (!entry->line2 || entry->line2 == NON_UNIQUE) + continue; + i = binary_search(sequence, longest, entry); + entry->previous = i < 0 ? NULL : sequence[i]; + sequence[++i] = entry; + if (i == longest) + longest++; + } + + /* No common unique lines were found */ + if (!longest) { + xdl_free(sequence); + return NULL; + } + + /* Iterate starting at the last element, adjusting the "next" members */ + entry = sequence[longest - 1]; + entry->next = NULL; + while (entry->previous) { + entry->previous->next = entry; + entry = entry->previous; + } + xdl_free(sequence); + return entry; +} + +static int match(struct hashmap *map, int line1, int line2) +{ + xrecord_t *record1 = map->env->xdf1.recs[line1 - 1]; + xrecord_t *record2 = map->env->xdf2.recs[line2 - 1]; + return xdl_recmatch(record1->ptr, record1->size, + record2->ptr, record2->size, map->xpp->flags); +} + +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2); + +static int walk_common_sequence(struct hashmap *map, struct entry *first, + int line1, int count1, int line2, int count2) +{ + int end1 = line1 + count1, end2 = line2 + count2; + int next1, next2; + + for (;;) { + /* Try to grow the line ranges of common lines */ + if (first) { + next1 = first->line1; + next2 = first->line2; + while (next1 > line1 && next2 > line2 && + match(map, next1 - 1, next2 - 1)) { + next1--; + next2--; + } + } else { + next1 = end1; + next2 = end2; + } + while (line1 < next1 && line2 < next2 && + match(map, line1, line2)) { + line1++; + line2++; + } + + /* Recurse */ + if (next1 > line1 || next2 > line2) { + struct hashmap submap; + + memset(&submap, 0, sizeof(submap)); + if (patience_diff(map->file1, map->file2, + map->xpp, map->env, + line1, next1 - line1, + line2, next2 - line2)) + return -1; + } + + if (!first) + return 0; + + while (first->next && + first->next->line1 == first->line1 + 1 && + first->next->line2 == first->line2 + 1) + first = first->next; + + line1 = first->line1 + 1; + line2 = first->line2 + 1; + + first = first->next; + } +} + +static int fall_back_to_classic_diff(struct hashmap *map, + int line1, int count1, int line2, int count2) +{ + /* + * This probably does not work outside Git, since + * we have a very simple mmfile structure. + * + * Note: ideally, we would reuse the prepared environment, but + * the libxdiff interface does not (yet) allow for diffing only + * ranges of lines instead of the whole files. + */ + mmfile_t subfile1, subfile2; + xpparam_t xpp; + xdfenv_t env; + + subfile1.ptr = (char *)map->env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = map->env->xdf1.recs[line1 + count1 - 2]->ptr + + map->env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)map->env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = map->env->xdf2.recs[line2 + count2 - 2]->ptr + + map->env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF; + if (xdl_do_diff(&subfile1, &subfile2, &xpp, &env) < 0) + return -1; + + memcpy(map->env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(map->env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} + +/* + * Recursively find the longest common sequence of unique lines, + * and if none was found, ask xdl_do_diff() to do the job. + * + * This function assumes that env was prepared with xdl_prepare_env(). + */ +static int patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env, + int line1, int count1, int line2, int count2) +{ + struct hashmap map; + struct entry *first; + int result = 0; + + /* trivial case: one side is empty */ + if (!count1) { + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + return 0; + } else if (!count2) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + return 0; + } + + memset(&map, 0, sizeof(map)); + if (fill_hashmap(file1, file2, xpp, env, &map, + line1, count1, line2, count2)) + return -1; + + /* are there any matching lines at all? */ + if (!map.has_matches) { + while(count1--) + env->xdf1.rchg[line1++ - 1] = 1; + while(count2--) + env->xdf2.rchg[line2++ - 1] = 1; + xdl_free(map.entries); + return 0; + } + + first = find_longest_common_sequence(&map); + if (first) + result = walk_common_sequence(&map, first, + line1, count1, line2, count2); + else + result = fall_back_to_classic_diff(&map, + line1, count1, line2, count2); + + xdl_free(map.entries); + return result; +} + +int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2, + xpparam_t const *xpp, xdfenv_t *env) +{ + if (xdl_prepare_env(file1, file2, xpp, env) < 0) + return -1; + + /* environment is cleaned up in xdl_diff() */ + return patience_diff(file1, file2, xpp, env, + 1, env->xdf1.nrec, 1, env->xdf2.nrec); +} diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c index a43aa72cd..168908523 100644 --- a/xdiff/xprepare.c +++ b/xdiff/xprepare.c @@ -290,7 +290,8 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdl_free_classifier(&cf); - if (xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) { + if (!(xpp->flags & XDF_PATIENCE_DIFF) && + xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) { xdl_free_ctx(&xe->xdf2); xdl_free_ctx(&xe->xdf1); |