diff options
78 files changed, 1045 insertions, 500 deletions
diff --git a/.gitignore b/.gitignore index 2bcc604afe..52d61f3193 100644 --- a/.gitignore +++ b/.gitignore @@ -107,6 +107,7 @@ git-ssh-push git-ssh-upload git-status git-stripspace +git-svn git-svnimport git-symbolic-ref git-tag diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 8601949e80..90722c21fa 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -49,7 +49,7 @@ People on the git mailing list need to be able to read and comment on the changes you are submitting. It is important for a developer to be able to "quote" your changes, using standard e-mail tools, so that they may comment on specific portions of -your code. For this reason, all patches should be submited +your code. For this reason, all patches should be submitted "inline". WARNING: Be wary of your MUAs word-wrap corrupting your patch. Do not cut-n-paste your patch; you can lose tabs that way if you are not careful. diff --git a/Documentation/config.txt b/Documentation/config.txt index f075f19815..0b434c1f19 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -110,10 +110,31 @@ apply.whitespace:: Tells `git-apply` how to handle whitespaces, in the same way as the '--whitespace' option. See gitlink:git-apply[1]. +diff.color:: + When true (or `always`), always use colors in patch. + When false (or `never`), never. When set to `auto`, use + colors only when the output is to the terminal. + +diff.color.<slot>:: + Use customized color for diff colorization. `<slot>` + specifies which part of the patch to use the specified + color, and is one of `plain` (context text), `meta` + (metainformation), `frag` (hunk header), `old` (removed + lines), or `new` (added lines). The value for these + configuration variables can be one of: `normal`, `bold`, + `dim`, `ul`, `blink`, `reverse`, `reset`, `black`, + `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or + `white`. + diff.renameLimit:: The number of files to consider when performing the copy/rename detection; equivalent to the git diff option '-l'. +diff.renames:: + Tells git to detect renames. If set to any boolean value, it + will enable basic rename detection. If set to "copies" or + "copy", it will detect copies, as well. + format.headers:: Additional email headers to include in a patch to be submitted by mail. See gitlink:git-format-patch[1]. diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt index 1fbca83141..d2b0bd38de 100644 --- a/Documentation/cvs-migration.txt +++ b/Documentation/cvs-migration.txt @@ -93,7 +93,7 @@ machine where the repository is hosted. If you don't want to give them a full shell on the machine, there is a restricted shell which only allows users to do git pushes and pulls; see gitlink:git-shell[1]. -Put all the committers should in the same group, and make the repository +Put all the committers in the same group, and make the repository writable by that group: ------------------------------------------------ diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index f523ec2fbe..47ba9a403a 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -4,18 +4,21 @@ -u:: Synonym for "-p". +--raw:: + Generate the raw format. + --patch-with-raw:: - Generate patch but keep also the default raw diff output. + Synonym for "-p --raw". --stat:: - Generate a diffstat instead of a patch. + Generate a diffstat. --summary:: Output a condensed summary of extended header information such as creations, renames and mode changes. --patch-with-stat:: - Generate patch and prepend its diffstat. + Synonym for "-p --stat". -z:: \0 line termination on output @@ -26,10 +29,25 @@ --name-status:: Show only names and status of changed files. +--color:: + Show colored diff. + +--no-color:: + Turn off colored diff, even when the configuration file + gives the default to color output. + +--no-renames:: + Turn off rename detection, even when the configuration + file gives the default to do so. + --full-index:: Instead of the first handful characters, show full object name of pre- and post-image blob on the "index" - line when generating a patch format output. + line when generating a patch format output. + +--binary:: + In addition to --full-index, output "binary diff" that + can be applied with "git apply". --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object @@ -94,5 +112,11 @@ Swap two inputs; that is, show differences from index or on-disk file to tree contents. +--text:: + Treat all files as text. + +-a:: + Shorthand for "--text". + For more detailed explanation on these common options, see also link:diffcore.html[diffcore documentation]. diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 56bd3e517d..27ac72d98f 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -8,7 +8,7 @@ git-cvsexportcommit - Export a commit to a CVS checkout SYNOPSIS -------- -'git-cvsexportcommmit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID +'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID DESCRIPTION diff --git a/Documentation/git-diff-files.txt b/Documentation/git-diff-files.txt index 481b8b3aa0..7248b35d95 100644 --- a/Documentation/git-diff-files.txt +++ b/Documentation/git-diff-files.txt @@ -37,7 +37,7 @@ omit diff output for unmerged entries and just show "Unmerged". commit with these flags. -q:: - Remain silent even on nonexisting files + Remain silent even on nonexistent files Output format ------------- diff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt index 7ab2080376..228c4d95bd 100644 --- a/Documentation/git-diff.txt +++ b/Documentation/git-diff.txt @@ -8,24 +8,24 @@ git-diff - Show changes between commits, commit and working tree, etc SYNOPSIS -------- -'git-diff' [ --diff-options ] <ent>{0,2} [<path>...] +'git-diff' [ --diff-options ] <tree-ish>{0,2} [<path>...] DESCRIPTION ----------- -Show changes between two ents, an ent and the working tree, an -ent and the index file, or the index file and the working tree. +Show changes between two trees, a tree and the working tree, a +tree and the index file, or the index file and the working tree. The combination of what is compared with what is determined by -the number of ents given to the command. +the number of trees given to the command. -* When no <ent> is given, the working tree and the index - file is compared, using `git-diff-files`. +* When no <tree-ish> is given, the working tree and the index + file are compared, using `git-diff-files`. -* When one <ent> is given, the working tree and the named - tree is compared, using `git-diff-index`. The option +* When one <tree-ish> is given, the working tree and the named + tree are compared, using `git-diff-index`. The option `--cached` can be given to compare the index file and the named tree. -* When two <ent>s are given, these two trees are compared +* When two <tree-ish>s are given, these two trees are compared using `git-diff-tree`. OPTIONS diff --git a/Documentation/git-init-db.txt b/Documentation/git-init-db.txt index 8a150d861f..0a4fc14b97 100644 --- a/Documentation/git-init-db.txt +++ b/Documentation/git-init-db.txt @@ -25,7 +25,7 @@ DESCRIPTION ----------- This command creates an empty git repository - basically a `.git` directory with subdirectories for `objects`, `refs/heads`, `refs/tags`, and -templated files. +template files. An initial `HEAD` file that references the HEAD of the master branch is also created. diff --git a/Documentation/git-mailsplit.txt b/Documentation/git-mailsplit.txt index 209e36bacb..5a17801f6a 100644 --- a/Documentation/git-mailsplit.txt +++ b/Documentation/git-mailsplit.txt @@ -25,7 +25,7 @@ OPTIONS -b:: If any file doesn't begin with a From line, assume it is a - single mail message instead of signalling error. + single mail message instead of signaling error. -d<prec>:: Instead of the default 4 digits with leading zeros, diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 4ce799b520..bebf30ad3d 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -83,7 +83,7 @@ your local modifications interfere with the merge, again, it stops before touching anything. So in the above two "failed merge" case, you do not have to -worry about lossage of data --- you simply were not ready to do +worry about loss of data --- you simply were not ready to do a merge, so no merge happened at all. You may want to finish whatever you were in the middle of doing, and retry the same pull after you are done and ready. diff --git a/Documentation/git-p4import.txt b/Documentation/git-p4import.txt index 0858e5efbe..ee9e8fa909 100644 --- a/Documentation/git-p4import.txt +++ b/Documentation/git-p4import.txt @@ -128,7 +128,7 @@ Tags A git tag of the form p4/xx is created for every change imported from the Perforce repository where xx is the Perforce changeset number. Therefore after the import you can use git to access any commit by its -Perforce number, eg. git show p4/327. +Perforce number, e.g. git show p4/327. The tag associated with the HEAD commit is also how `git-p4import` determines if there are new changes to incrementally import from the @@ -143,7 +143,7 @@ may delete the tags. Notes ----- -You can interrupt the import (eg. ctrl-c) at any time and restart it +You can interrupt the import (e.g. ctrl-c) at any time and restart it without worry. Author information is automatically determined by querying the diff --git a/Documentation/git-pack-redundant.txt b/Documentation/git-pack-redundant.txt index 8fb0659438..7d54b17e37 100644 --- a/Documentation/git-pack-redundant.txt +++ b/Documentation/git-pack-redundant.txt @@ -29,7 +29,7 @@ OPTIONS --all:: - Processes all packs. Any filenames on the commandline are ignored. + Processes all packs. Any filenames on the command line are ignored. --alt-odb:: Don't require objects present in packs from alternate object diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index d5b5ca167c..56afd64f42 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -67,7 +67,7 @@ Some short-cut notations are also supported. -f, \--force:: Usually, the command refuses to update a remote ref that is - not a descendent of the local ref used to overwrite it. + not a descendant of the local ref used to overwrite it. This flag disables the check. This can cause the remote repository to lose commits; use it with care. diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 803c0d5cae..b03d66f61c 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -119,8 +119,8 @@ you can set the filemode to true with % git repo-config core.filemode true ------------ -The hypothetic proxy command entries actually have a postfix to discern -to what URL they apply. Here is how to change the entry for kernel.org +The hypothetical proxy command entries actually have a postfix to discern +what URL they apply to. Here is how to change the entry for kernel.org to "ssh". ------------ diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index ad6d14c55a..e220842981 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -15,6 +15,7 @@ SYNOPSIS [ \--sparse ] [ \--no-merges ] [ \--remove-empty ] + [ \--not ] [ \--all ] [ \--topo-order ] [ \--parents ] @@ -37,6 +38,14 @@ not in 'baz'". A special notation <commit1>..<commit2> can be used as a short-hand for {caret}<commit1> <commit2>. +Another special notation is <commit1>...<commit2> which is useful for +merges. The resulting set of commits is the symmetric difference +between the two operands. The following two commands are equivalent: + +------------ +$ git-rev-list A B --not $(git-merge-base --all A B) +$ git-rev-list A...B +------------ OPTIONS ------- @@ -55,7 +64,7 @@ OPTIONS --objects-edge:: Similar to `--objects`, but also print the IDs of - excluded commits refixed with a `-` character. This is + excluded commits prefixed with a `-` character. This is used by `git-pack-objects` to build 'thin' pack, which records objects in deltified form based on objects contained in these excluded commits to reduce network @@ -93,6 +102,11 @@ OPTIONS --remove-empty:: Stop when a given path disappears from the tree. +--not:: + Reverses the meaning of the '{caret}' prefix (or lack + thereof) for all following revision specifiers, up to + the next `--not`. + --all:: Pretend as if all the refs in `$GIT_DIR/refs/` are listed on the command line as <commit>. diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 627cde8520..b761b4b965 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -156,11 +156,6 @@ syntax. and dereference the tag recursively until a non-tag object is found. -'git-rev-parse' also accepts a prefix '{caret}' to revision parameter, -which is passed to 'git-rev-list'. Two revision parameters -concatenated with '..' is a short-hand for writing a range -between them. I.e. 'r1..r2' is equivalent to saying '{caret}r1 r2' - Here is an illustration, by Jon Loeliger. Both node B and C are a commit parents of commit node A. Parent commits are ordered left-to-right. @@ -168,9 +163,9 @@ left-to-right. G H I J \ / \ / D E F - \ | / - \ | / - \|/ + \ | / \ + \ | / | + \|/ | B C \ / \ / @@ -188,6 +183,40 @@ left-to-right. J = F^2 = B^3^2 = A^^3^2 +SPECIFYING RANGES +----------------- + +History traversing commands such as `git-log` operate on a set +of commits, not just a single commit. To these commands, +specifying a single revision with the notation described in the +previous section means the set of commits reachable from that +commit, following the commit ancestry chain. + +To exclude commits reachable from a commit, a prefix `{caret}` +notation is used. E.g. "`{caret}r1 r2`" means commits reachable +from `r2` but exclude the ones reachable from `r1`. + +This set operation appears so often that there is a shorthand +for it. "`r1..r2`" is equivalent to "`{caret}r1 r2`". It is +the difference of two sets (subtract the set of commits +reachable from `r1` from the set of commits reachable from +`r2`). + +A similar notation "`r1\...r2`" is called symmetric difference +of `r1` and `r2` and is defined as +"`r1 r2 --not $(git-merge-base --all r1 r2)`". +It it the set of commits that are reachable from either one of +`r1` or `r2` but not from both. + +Here are a few examples: + + D A B D + D F A B C D F + ^A G B D + ^A F B C F + G...I C D F G I + ^B G I C D F G I + Author ------ Written by Linus Torvalds <torvalds@osdl.org> and diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index f115b45ef6..a2445a48fc 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -52,6 +52,11 @@ OPTIONS appear in topological order (i.e., descendant commits are shown before their parents). +--sparse:: + By default, the output omits merges that are reachable + from only one tip being shown. This option makes them + visible. + --more=<n>:: Usually the command stops output upon showing the commit that is the common ancestor of all the branches. This diff --git a/contrib/git-svn/git-svn.txt b/Documentation/git-svn.txt index f7d3de48f0..7d86809844 100644 --- a/contrib/git-svn/git-svn.txt +++ b/Documentation/git-svn.txt @@ -224,7 +224,7 @@ Merge tracking in Subversion is lacking and doing branched development with Subversion is cumbersome as a result. git-svn completely forgoes any automated merge/branch tracking on the Subversion side and leaves it entirely up to the user on the git side. It's simply not worth it to do -a useful translation when the the original signal is weak. +a useful translation when the original signal is weak. TRACKING MULTIPLE REPOSITORIES OR BRANCHES ------------------------------------------ diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt index d79523f56d..0914cbb0ba 100644 --- a/Documentation/git-tools.txt +++ b/Documentation/git-tools.txt @@ -42,7 +42,7 @@ History Viewers - *gitk* (shipped with git-core) - gitk is a simple TK GUI for browsing history of GIT repositories easily. + gitk is a simple Tk GUI for browsing history of GIT repositories easily. - *gitview* (contrib/) diff --git a/Documentation/git-upload-tar.txt b/Documentation/git-upload-tar.txt index a1019a0231..394af62015 100644 --- a/Documentation/git-upload-tar.txt +++ b/Documentation/git-upload-tar.txt @@ -17,7 +17,7 @@ to the other end over the git protocol. This command is usually not invoked directly by the end user. The UI for the protocol is on the 'git-tar-tree' side, and the -program pair is meant to be used to get a tar achive from a +program pair is meant to be used to get a tar archive from a remote repository. diff --git a/Documentation/git.txt b/Documentation/git.txt index 51f20c6e67..d00cc3ea52 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -478,7 +478,7 @@ Configuration Mechanism Starting from 0.99.9 (actually mid 0.99.8.GIT), `.git/config` file is used to hold per-repository configuration options. It is a -simple text file modelled after `.ini` format familiar to some +simple text file modeled after `.ini` format familiar to some people. Here is an example: ------------ diff --git a/Documentation/glossary.txt b/Documentation/glossary.txt index 116ddb7fbf..14449ca8ba 100644 --- a/Documentation/glossary.txt +++ b/Documentation/glossary.txt @@ -86,7 +86,7 @@ directory:: ent:: Favorite synonym to "tree-ish" by some total geeks. See `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth - explanation. + explanation. Avoid this term, not to confuse people. fast forward:: A fast-forward is a special type of merge where you have diff --git a/Documentation/howto/isolate-bugs-with-bisect.txt b/Documentation/howto/isolate-bugs-with-bisect.txt index edbcd4c661..926bbdc3cb 100644 --- a/Documentation/howto/isolate-bugs-with-bisect.txt +++ b/Documentation/howto/isolate-bugs-with-bisect.txt @@ -28,7 +28,7 @@ Then do and at this point "git bisect" will churn for a while, and tell you what the mid-point between those two commits are, and check that state out as -the head of the bew "bisect" branch. +the head of the new "bisect" branch. Compile and reboot. diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt index c2d4a91c7c..fcd64e9b9b 100644 --- a/Documentation/howto/rebase-from-internal-branch.txt +++ b/Documentation/howto/rebase-from-internal-branch.txt @@ -124,7 +124,7 @@ up your changes, along with other changes. The two commits #2' and #3' in the above picture record the same changes your e-mail submission for #2 and #3 contained, but -probably with the new sign-off line added by the upsteam +probably with the new sign-off line added by the upstream maintainer and definitely with different committer and ancestry information, they are different objects from #2 and #3 commits. diff --git a/Documentation/technical/pack-heuristics.txt b/Documentation/technical/pack-heuristics.txt index 9aadd5cee5..103eb5d989 100644 --- a/Documentation/technical/pack-heuristics.txt +++ b/Documentation/technical/pack-heuristics.txt @@ -73,7 +73,7 @@ The traditional insight: <pasky> yes -And Bable-like confusion flowed. +And Babel-like confusion flowed. <njs`> oh, hmm, and I'm not sure what this sliding window means either @@ -257,7 +257,7 @@ proclaim it a non-issue. Good style too! (type, basename, size)). Then we walk through this list, and calculate a delta of - each object against the last n (tunable paramater) objects, + each object against the last n (tunable parameter) objects, and pick the smallest of these deltas. Vastly simplified, but the essence is there! @@ -395,7 +395,7 @@ used as setup for a later optimization, which is a real word: do "object name->location in packfile" translation. <njs`> I'm assuming the real win for delta-ing large->small is - more homogenous statistics for gzip to run over? + more homogeneous statistics for gzip to run over? (You have to put the bytes in one place or another, but putting them in a larger blob wins on compression) @@ -448,7 +448,7 @@ design options, etc. Bugs happen, but they are "simple" bugs. And bugs that actually get some object store detail wrong are almost always - so obious that they never go anywhere. + so obvious that they never go anywhere. <njs`> Yeah. diff --git a/Documentation/urls.txt b/Documentation/urls.txt index 74774134e3..d60b37147a 100644 --- a/Documentation/urls.txt +++ b/Documentation/urls.txt @@ -47,7 +47,7 @@ Then such a short-hand is specified in place of <repository> without <refspec> parameters on the command line, <refspec> specified on `Push:` lines or `Pull:` lines are used for `git-push` and `git-fetch`/`git-pull`, -respectively. Multiple `Push:` and and `Pull:` lines may +respectively. Multiple `Push:` and `Pull:` lines may be specified for additional branch mappings. The name of a file in `$GIT_DIR/branches` directory can be @@ -44,7 +44,7 @@ Issues of note: - "libcurl" and "curl" executable. git-http-fetch and git-fetch use them. If you do not use http - transfer, you are probabaly OK if you do not have + transfer, you are probably OK if you do not have them. - expat library; git-http-push uses it for remote lock @@ -69,7 +69,7 @@ Issues of note: git, and if you only use git to track other peoples work you'll never notice the lack of it. - - "wish", the TCL/Tk windowing shell is used in gitk to show the + - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically - "ssh" is used to push and pull over the net @@ -33,6 +33,10 @@ all: # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. # Enable it on Windows. By default, symrefs are still used. # +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability +# tests. These tests take up a significant amount of the total test time +# but are not needed unless you plan to talk to SVN repos. +# # Define PPC_SHA1 environment variable when running make to make use of # a bundled SHA1 routine optimized for PowerPC. # @@ -60,7 +64,7 @@ all: # Define NO_ACCURATE_DIFF if your diff program at least sometimes misses # a missing newline at the end of the file. # -# Define NO_PYTHON if you want to loose all benefits of the recursive merge. +# Define NO_PYTHON if you want to lose all benefits of the recursive merge. # # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you @@ -134,7 +138,7 @@ SCRIPT_PERL = \ git-shortlog.perl git-rerere.perl \ git-annotate.perl git-cvsserver.perl \ git-svnimport.perl git-mv.perl git-cvsexportcommit.perl \ - git-send-email.perl + git-send-email.perl git-svn.perl SCRIPT_PYTHON = \ git-merge-recursive.py @@ -469,7 +473,7 @@ ifdef NO_ACCURATE_DIFF ALL_CFLAGS += -DNO_ACCURATE_DIFF endif -# Shell quote (do not use $(call) to accomodate ancient setups); +# Shell quote (do not use $(call) to accommodate ancient setups); SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER)) @@ -514,6 +518,7 @@ common-cmds.h: Documentation/git-*.txt $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ @@ -552,9 +557,9 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \ - -e '/@@GITWEB_CGI@@/rgitweb/gitweb.cgi' \ + -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \ -e '/@@GITWEB_CGI@@/d' \ - -e '/@@GITWEB_CSS@@/rgitweb/gitweb.css' \ + -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ $@.sh > $@+ chmod +x $@+ @@ -652,6 +657,7 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS # with that. export NO_PYTHON +export NO_SVN_TESTS test: all $(MAKE) -C t/ all diff --git a/builtin-add.c b/builtin-add.c index bfbbb1bf52..2d25698173 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -181,7 +181,7 @@ int cmd_add(int argc, const char **argv, char **envp) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || - commit_lock_file(&lock_file)) + close(newfd) || commit_lock_file(&lock_file)) die("Unable to write new index file"); } diff --git a/builtin-apply.c b/builtin-apply.c index e9ead002d3..c3af48917c 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2323,7 +2323,7 @@ int cmd_apply(int argc, const char **argv, char **envp) if (write_index) { if (write_cache(newfd, active_cache, active_nr) || - commit_lock_file(&lock_file)) + close(newfd) || commit_lock_file(&lock_file)) die("Unable to write new index file"); } diff --git a/builtin-diff-files.c b/builtin-diff-files.c index a655eea91e..81ac2fe64a 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -18,7 +18,7 @@ int cmd_diff_files(int argc, const char **argv, char **envp) struct rev_info rev; int silent = 0; - git_config(git_diff_config); + git_config(git_default_config); /* no "diff" UI options */ init_revisions(&rev); rev.abbrev = 0; diff --git a/builtin-diff-index.c b/builtin-diff-index.c index b37c9e8ccb..a1fa1b85cf 100644 --- a/builtin-diff-index.c +++ b/builtin-diff-index.c @@ -15,7 +15,7 @@ int cmd_diff_index(int argc, const char **argv, char **envp) int cached = 0; int i; - git_config(git_diff_config); + git_config(git_default_config); /* no "diff" UI options */ init_revisions(&rev); rev.abbrev = 0; diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c index 30931fe049..9c62702941 100644 --- a/builtin-diff-stages.c +++ b/builtin-diff-stages.c @@ -61,7 +61,7 @@ int cmd_diff_stages(int ac, const char **av, char **envp) const char *prefix = setup_git_directory(); const char **pathspec = NULL; - git_config(git_diff_config); + git_config(git_default_config); /* no "diff" UI options */ read_cache(); diff_setup(&diff_options); while (1 < ac && av[1][0] == '-') { diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index ae1cde9d00..b610668594 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -67,7 +67,7 @@ int cmd_diff_tree(int argc, const char **argv, char **envp) static struct rev_info *opt = &log_tree_opt; int read_stdin = 0; - git_config(git_diff_config); + git_config(git_default_config); /* no "diff" UI options */ nr_sha1 = 0; init_revisions(opt); opt->abbrev = 0; diff --git a/builtin-diff.c b/builtin-diff.c index d520c7ca29..1df531ba28 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -250,7 +250,7 @@ int cmd_diff(int argc, const char **argv, char **envp) * Other cases are errors. */ - git_config(git_diff_config); + git_config(git_diff_ui_config); init_revisions(&rev); argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/builtin-grep.c b/builtin-grep.c index 6973c66704..743ca8c692 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -82,17 +82,47 @@ static int pathspec_matches(const char **paths, const char *name) return 0; } +enum grep_pat_token { + GREP_PATTERN, + GREP_AND, + GREP_OPEN_PAREN, + GREP_CLOSE_PAREN, + GREP_NOT, + GREP_OR, +}; + struct grep_pat { struct grep_pat *next; const char *origin; int no; + enum grep_pat_token token; const char *pattern; regex_t regexp; }; +enum grep_expr_node { + GREP_NODE_ATOM, + GREP_NODE_NOT, + GREP_NODE_AND, + GREP_NODE_OR, +}; + +struct grep_expr { + enum grep_expr_node node; + union { + struct grep_pat *atom; + struct grep_expr *unary; + struct { + struct grep_expr *left; + struct grep_expr *right; + } binary; + } u; +}; + struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; + struct grep_expr *pattern_expression; regex_t regexp; unsigned linenum:1; unsigned invert:1; @@ -105,43 +135,224 @@ struct grep_opt { #define GREP_BINARY_NOMATCH 1 #define GREP_BINARY_TEXT 2 unsigned binary:2; + unsigned extended:1; int regflags; unsigned pre_context; unsigned post_context; }; static void add_pattern(struct grep_opt *opt, const char *pat, - const char *origin, int no) + const char *origin, int no, enum grep_pat_token t) { struct grep_pat *p = xcalloc(1, sizeof(*p)); p->pattern = pat; p->origin = origin; p->no = no; + p->token = t; *opt->pattern_tail = p; opt->pattern_tail = &p->next; p->next = NULL; } +static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) +{ + int err = regcomp(&p->regexp, p->pattern, opt->regflags); + if (err) { + char errbuf[1024]; + char where[1024]; + if (p->no) + sprintf(where, "In '%s' at %d, ", + p->origin, p->no); + else if (p->origin) + sprintf(where, "%s, ", p->origin); + else + where[0] = 0; + regerror(err, &p->regexp, errbuf, 1024); + regfree(&p->regexp); + die("%s'%s': %s", where, p->pattern, errbuf); + } +} + +#if DEBUG +static inline void indent(int in) +{ + int i; + for (i = 0; i < in; i++) putchar(' '); +} + +static void dump_pattern_exp(struct grep_expr *x, int in) +{ + switch (x->node) { + case GREP_NODE_ATOM: + indent(in); + puts(x->u.atom->pattern); + break; + case GREP_NODE_NOT: + indent(in); + puts("--not"); + dump_pattern_exp(x->u.unary, in+1); + break; + case GREP_NODE_AND: + dump_pattern_exp(x->u.binary.left, in+1); + indent(in); + puts("--and"); + dump_pattern_exp(x->u.binary.right, in+1); + break; + case GREP_NODE_OR: + dump_pattern_exp(x->u.binary.left, in+1); + indent(in); + puts("--or"); + dump_pattern_exp(x->u.binary.right, in+1); + break; + } +} + +static void looking_at(const char *msg, struct grep_pat **list) +{ + struct grep_pat *p = *list; + fprintf(stderr, "%s: looking at ", msg); + if (!p) + fprintf(stderr, "empty\n"); + else + fprintf(stderr, "<%s>\n", p->pattern); +} +#else +#define looking_at(a,b) do {} while(0) +#endif + +static struct grep_expr *compile_pattern_expr(struct grep_pat **); +static struct grep_expr *compile_pattern_atom(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + looking_at("atom", list); + + p = *list; + switch (p->token) { + case GREP_PATTERN: /* atom */ + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_ATOM; + x->u.atom = p; + *list = p->next; + return x; + case GREP_OPEN_PAREN: + *list = p->next; + x = compile_pattern_expr(list); + if (!x) + return NULL; + if (!*list || (*list)->token != GREP_CLOSE_PAREN) + die("unmatched parenthesis"); + *list = (*list)->next; + return x; + default: + return NULL; + } +} + +static struct grep_expr *compile_pattern_not(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x; + + looking_at("not", list); + + p = *list; + switch (p->token) { + case GREP_NOT: + if (!p->next) + die("--not not followed by pattern expression"); + *list = p->next; + x = xcalloc(1, sizeof (struct grep_expr)); + x->node = GREP_NODE_NOT; + x->u.unary = compile_pattern_not(list); + if (!x->u.unary) + die("--not followed by non pattern expression"); + return x; + default: + return compile_pattern_atom(list); + } +} + +static struct grep_expr *compile_pattern_and(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + looking_at("and", list); + + x = compile_pattern_not(list); + p = *list; + if (p && p->token == GREP_AND) { + if (!p->next) + die("--and not followed by pattern expression"); + *list = p->next; + y = compile_pattern_and(list); + if (!y) + die("--and not followed by pattern expression"); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_AND; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_or(struct grep_pat **list) +{ + struct grep_pat *p; + struct grep_expr *x, *y, *z; + + looking_at("or", list); + + x = compile_pattern_and(list); + p = *list; + if (x && p && p->token != GREP_CLOSE_PAREN) { + y = compile_pattern_or(list); + if (!y) + die("not a pattern expression %s", p->pattern); + z = xcalloc(1, sizeof (struct grep_expr)); + z->node = GREP_NODE_OR; + z->u.binary.left = x; + z->u.binary.right = y; + return z; + } + return x; +} + +static struct grep_expr *compile_pattern_expr(struct grep_pat **list) +{ + looking_at("expr", list); + + return compile_pattern_or(list); +} + static void compile_patterns(struct grep_opt *opt) { struct grep_pat *p; + + /* First compile regexps */ for (p = opt->pattern_list; p; p = p->next) { - int err = regcomp(&p->regexp, p->pattern, opt->regflags); - if (err) { - char errbuf[1024]; - char where[1024]; - if (p->no) - sprintf(where, "In '%s' at %d, ", - p->origin, p->no); - else if (p->origin) - sprintf(where, "%s, ", p->origin); - else - where[0] = 0; - regerror(err, &p->regexp, errbuf, 1024); - regfree(&p->regexp); - die("%s'%s': %s", where, p->pattern, errbuf); - } + if (p->token == GREP_PATTERN) + compile_regexp(p, opt); + else + opt->extended = 1; } + + if (!opt->extended) + return; + + /* Then bundle them up in an expression. + * A classic recursive descent parser would do. + */ + p = opt->pattern_list; + opt->pattern_expression = compile_pattern_expr(&p); +#if DEBUG + dump_pattern_exp(opt->pattern_expression, 0); +#endif + if (p) + die("incomplete pattern expression: %s", p->pattern); } static char *end_of_line(char *cp, unsigned long *left) @@ -196,6 +407,79 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match) } } +static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol) +{ + int hit = 0; + regmatch_t pmatch[10]; + + if (!opt->fixed) { + regex_t *exp = &p->regexp; + hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), + pmatch, 0); + } + else { + hit = !fixmatch(p->pattern, bol, pmatch); + } + + if (hit && opt->word_regexp) { + /* Match beginning must be either + * beginning of the line, or at word + * boundary (i.e. the last char must + * not be alnum or underscore). + */ + if ((pmatch[0].rm_so < 0) || + (eol - bol) <= pmatch[0].rm_so || + (pmatch[0].rm_eo < 0) || + (eol - bol) < pmatch[0].rm_eo) + die("regexp returned nonsense"); + if (pmatch[0].rm_so != 0 && + word_char(bol[pmatch[0].rm_so-1])) + hit = 0; + if (pmatch[0].rm_eo != (eol-bol) && + word_char(bol[pmatch[0].rm_eo])) + hit = 0; + } + return hit; +} + +static int match_expr_eval(struct grep_opt *opt, + struct grep_expr *x, + char *bol, char *eol) +{ + switch (x->node) { + case GREP_NODE_ATOM: + return match_one_pattern(opt, x->u.atom, bol, eol); + break; + case GREP_NODE_NOT: + return !match_expr_eval(opt, x->u.unary, bol, eol); + case GREP_NODE_AND: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) && + match_expr_eval(opt, x->u.binary.right, bol, eol)); + case GREP_NODE_OR: + return (match_expr_eval(opt, x->u.binary.left, bol, eol) || + match_expr_eval(opt, x->u.binary.right, bol, eol)); + } + die("Unexpected node type (internal error) %d\n", x->node); +} + +static int match_expr(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_expr *x = opt->pattern_expression; + return match_expr_eval(opt, x, bol, eol); +} + +static int match_line(struct grep_opt *opt, char *bol, char *eol) +{ + struct grep_pat *p; + if (opt->extended) + return match_expr(opt, bol, eol); + for (p = opt->pattern_list; p; p = p->next) { + if (match_one_pattern(opt, p, bol, eol)) + return 1; + } + return 0; +} + static int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size) { @@ -231,46 +515,15 @@ static int grep_buffer(struct grep_opt *opt, const char *name, hunk_mark = "--\n"; while (left) { - regmatch_t pmatch[10]; char *eol, ch; int hit = 0; - struct grep_pat *p; eol = end_of_line(bol, &left); ch = *eol; *eol = 0; - for (p = opt->pattern_list; p; p = p->next) { - if (!opt->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { - hit = !fixmatch(p->pattern, bol, pmatch); - } + hit = match_line(opt, bol, eol); - if (hit && opt->word_regexp) { - /* Match beginning must be either - * beginning of the line, or at word - * boundary (i.e. the last char must - * not be alnum or underscore). - */ - if ((pmatch[0].rm_so < 0) || - (eol - bol) <= pmatch[0].rm_so || - (pmatch[0].rm_eo < 0) || - (eol - bol) < pmatch[0].rm_eo) - die("regexp returned nonsense"); - if (pmatch[0].rm_so != 0 && - word_char(bol[pmatch[0].rm_so-1])) - hit = 0; - if (pmatch[0].rm_eo != (eol-bol) && - word_char(bol[pmatch[0].rm_eo])) - hit = 0; - } - if (hit) - break; - } /* "grep -v -e foo -e bla" should list lines * that do not have either, so inversion should * be done outside. @@ -452,6 +705,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) char *argptr = randarg; struct grep_pat *p; + if (opt->extended) + return -1; len = nr = 0; push_arg("grep"); if (opt->fixed) @@ -813,16 +1068,36 @@ int cmd_grep(int argc, const char **argv, char **envp) /* ignore empty line like grep does */ if (!buf[0]) continue; - add_pattern(&opt, strdup(buf), argv[1], ++lno); + add_pattern(&opt, strdup(buf), argv[1], ++lno, + GREP_PATTERN); } fclose(patterns); argv++; argc--; continue; } + if (!strcmp("--not", arg)) { + add_pattern(&opt, arg, "command line", 0, GREP_NOT); + continue; + } + if (!strcmp("--and", arg)) { + add_pattern(&opt, arg, "command line", 0, GREP_AND); + continue; + } + if (!strcmp("--or", arg)) + continue; /* no-op */ + if (!strcmp("(", arg)) { + add_pattern(&opt, arg, "command line", 0, GREP_OPEN_PAREN); + continue; + } + if (!strcmp(")", arg)) { + add_pattern(&opt, arg, "command line", 0, GREP_CLOSE_PAREN); + continue; + } if (!strcmp("-e", arg)) { if (1 < argc) { - add_pattern(&opt, argv[1], "-e option", 0); + add_pattern(&opt, argv[1], "-e option", 0, + GREP_PATTERN); argv++; argc--; continue; @@ -840,7 +1115,8 @@ int cmd_grep(int argc, const char **argv, char **envp) /* First unrecognized non-option token */ if (!opt.pattern_list) { - add_pattern(&opt, arg, "command line", 0); + add_pattern(&opt, arg, "command line", 0, + GREP_PATTERN); break; } else { diff --git a/builtin-log.c b/builtin-log.c index 864c6cd9ea..7e5cab15c1 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -47,6 +47,7 @@ int cmd_whatchanged(int argc, const char **argv, char **envp) { struct rev_info rev; + git_config(git_diff_ui_config); init_revisions(&rev); rev.diff = 1; rev.diffopt.recursive = 1; @@ -61,6 +62,7 @@ int cmd_show(int argc, const char **argv, char **envp) { struct rev_info rev; + git_config(git_diff_ui_config); init_revisions(&rev); rev.diff = 1; rev.diffopt.recursive = 1; @@ -77,6 +79,7 @@ int cmd_log(int argc, const char **argv, char **envp) { struct rev_info rev; + git_config(git_diff_ui_config); init_revisions(&rev); rev.always_show_header = 1; cmd_log_init(argc, argv, envp, &rev); @@ -102,7 +105,10 @@ static int git_format_config(const char *var, const char *value) strcat(extra_headers, value); return 0; } - return git_default_config(var, value); + if (!strcmp(var, "diff.color")) { + return 0; + } + return git_diff_ui_config(var, value); } @@ -234,6 +240,7 @@ int cmd_format_patch(int argc, const char **argv, char **envp) struct diff_options patch_id_opts; char *add_signoff = NULL; + git_config(git_format_config); init_revisions(&rev); rev.commit_format = CMIT_FMT_EMAIL; rev.verbose_header = 1; @@ -243,7 +250,6 @@ int cmd_format_patch(int argc, const char **argv, char **envp) rev.diffopt.msg_sep = ""; rev.diffopt.recursive = 1; - git_config(git_format_config); rev.extra_headers = extra_headers; /* diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 3e40747cf5..ac53f76f68 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -348,7 +348,7 @@ static void cleanup_space(char *buf) } } -static void decode_header_bq(char *it); +static void decode_header(char *it); typedef int (*header_fn_t)(char *); struct header_def { const char *name; @@ -371,7 +371,7 @@ static void check_header(char *line, struct header_def *header) /* Unwrap inline B and Q encoding, and optionally * normalize the meta information to utf8. */ - decode_header_bq(line + len + 2); + decode_header(line + len + 2); header[i].func(line + len + 2); break; } @@ -566,16 +566,19 @@ static void convert_to_utf8(char *line, char *charset) #endif } -static void decode_header_bq(char *it) +static int decode_header_bq(char *it) { char *in, *out, *ep, *cp, *sp; char outbuf[1000]; + int rfc2047 = 0; in = it; out = outbuf; while ((ep = strstr(in, "=?")) != NULL) { int sz, encoding; char charset_q[256], piecebuf[256]; + rfc2047 = 1; + if (in != ep) { sz = ep - in; memcpy(out, in, sz); @@ -589,19 +592,19 @@ static void decode_header_bq(char *it) ep += 2; cp = strchr(ep, '?'); if (!cp) - return; /* no munging */ + return rfc2047; /* no munging */ for (sp = ep; sp < cp; sp++) charset_q[sp - ep] = tolower(*sp); charset_q[cp - ep] = 0; encoding = cp[1]; if (!encoding || cp[2] != '?') - return; /* no munging */ + return rfc2047; /* no munging */ ep = strstr(cp + 3, "?="); if (!ep) - return; /* no munging */ + return rfc2047; /* no munging */ switch (tolower(encoding)) { default: - return; /* no munging */ + return rfc2047; /* no munging */ case 'b': sz = decode_b_segment(cp + 3, piecebuf, ep); break; @@ -610,7 +613,7 @@ static void decode_header_bq(char *it) break; } if (sz < 0) - return; + return rfc2047; if (metainfo_charset) convert_to_utf8(piecebuf, charset_q); strcpy(out, piecebuf); @@ -619,6 +622,19 @@ static void decode_header_bq(char *it) } strcpy(out, in); strcpy(it, outbuf); + return rfc2047; +} + +static void decode_header(char *it) +{ + + if (decode_header_bq(it)) + return; + /* otherwise "it" is a straight copy of the input. + * This can be binary guck but there is no charset specified. + */ + if (metainfo_charset) + convert_to_utf8(it, ""); } static void decode_transfer_encoding(char *line) diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 9a2099d730..23a8d92a4b 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -1038,7 +1038,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp) } if (write_cache(newfd, active_cache, active_nr) || - commit_lock_file(&lock_file)) + close(newfd) || commit_lock_file(&lock_file)) die("unable to write new index file"); return 0; } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 5f5ade45ae..b3e4386c1b 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -164,6 +164,51 @@ static int show_file(const char *arg) return 0; } +static int try_difference(const char *arg) +{ + char *dotdot; + unsigned char sha1[20]; + unsigned char end[20]; + const char *next; + const char *this; + int symmetric; + + if (!(dotdot = strstr(arg, ".."))) + return 0; + next = dotdot + 2; + this = arg; + symmetric = (*next == '.'); + + *dotdot = 0; + next += symmetric; + + if (!*next) + next = "HEAD"; + if (dotdot == arg) + this = "HEAD"; + if (!get_sha1(this, sha1) && !get_sha1(next, end)) { + show_rev(NORMAL, end, next); + show_rev(symmetric ? NORMAL : REVERSED, sha1, this); + if (symmetric) { + struct commit_list *exclude; + struct commit *a, *b; + a = lookup_commit_reference(sha1); + b = lookup_commit_reference(end); + exclude = get_merge_bases(a, b, 1); + while (exclude) { + struct commit_list *n = exclude->next; + show_rev(REVERSED, + exclude->item->object.sha1,NULL); + free(exclude); + exclude = n; + } + } + return 1; + } + *dotdot = '.'; + return 0; +} + int cmd_rev_parse(int argc, const char **argv, char **envp) { int i, as_is = 0, verify = 0; @@ -174,7 +219,6 @@ int cmd_rev_parse(int argc, const char **argv, char **envp) for (i = 1; i < argc; i++) { const char *arg = argv[i]; - char *dotdot; if (as_is) { if (show_file(arg) && as_is < 2) @@ -326,23 +370,8 @@ int cmd_rev_parse(int argc, const char **argv, char **envp) } /* Not a flag argument */ - dotdot = strstr(arg, ".."); - if (dotdot) { - unsigned char end[20]; - const char *next = dotdot + 2; - const char *this = arg; - *dotdot = 0; - if (!*next) - next = "HEAD"; - if (dotdot == arg) - this = "HEAD"; - if (!get_sha1(this, sha1) && !get_sha1(next, end)) { - show_rev(NORMAL, end, next); - show_rev(REVERSED, sha1, this); - continue; - } - *dotdot = '.'; - } + if (try_difference(arg)) + continue; if (!get_sha1(arg, sha1)) { show_rev(NORMAL, sha1, arg); continue; diff --git a/builtin-rm.c b/builtin-rm.c index 4d56a1f070..875d8252fa 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -147,7 +147,7 @@ int cmd_rm(int argc, const char **argv, char **envp) if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || - commit_lock_file(&lock_file)) + close(newfd) || commit_lock_file(&lock_file)) die("Unable to write new index file"); } diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 09d8227862..260cb221b9 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -6,7 +6,7 @@ #include "builtin.h" static const char show_branch_usage[] = -"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; +"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; static int default_num = 0; static int default_alloc = 0; diff --git a/builtin-update-index.c b/builtin-update-index.c index ef50243452..1a4200d151 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -648,7 +648,7 @@ int cmd_update_index(int argc, const char **argv, char **envp) finish: if (active_cache_changed) { if (write_cache(newfd, active_cache, active_nr) || - commit_lock_file(lock_file)) + close(newfd) || commit_lock_file(lock_file)) die("Unable to write new index file"); } diff --git a/builtin-write-tree.c b/builtin-write-tree.c index 70e9b6fcc6..449a4d1b57 100644 --- a/builtin-write-tree.c +++ b/builtin-write-tree.c @@ -35,7 +35,8 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix) missing_ok, 0) < 0) die("git-write-tree: error building trees"); if (0 <= newfd) { - if (!write_cache(newfd, active_cache, active_nr)) + if (!write_cache(newfd, active_cache, active_nr) + && !close(newfd)) commit_lock_file(lock_file); } /* Not being able to write is fine -- we are only interested @@ -382,6 +382,7 @@ extern int receive_keep_pack(int fd[2], const char *me, int quiet, int); /* pager.c */ extern void setup_pager(void); +extern int pager_in_use; /* base85 */ int decode_85(char *dst, char *line, int linelen); diff --git a/checkout-index.c b/checkout-index.c index ea40bc29be..2927955508 100644 --- a/checkout-index.c +++ b/checkout-index.c @@ -311,7 +311,7 @@ int main(int argc, char **argv) if (0 <= newfd && (write_cache(newfd, active_cache, active_nr) || - commit_lock_file(&lock_file))) + close(newfd) || commit_lock_file(&lock_file))) die("Unable to write new index file"); return 0; } @@ -397,12 +397,13 @@ void clear_commit_marks(struct commit *commit, unsigned int mark) { struct commit_list *parents; - parents = commit->parents; commit->object.flags &= ~mark; + parents = commit->parents; while (parents) { struct commit *parent = parents->item; - if (parent && parent->object.parsed && - (parent->object.flags & mark)) + + /* Have we already cleared this? */ + if (mark & parent->object.flags) clear_commit_marks(parent, mark); parents = parents->next; } @@ -846,3 +847,247 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo, } free(nodes); } + +/* merge-rebase stuff */ + +/* bits #0..7 in revision.h */ +#define PARENT1 (1u<< 8) +#define PARENT2 (1u<< 9) +#define STALE (1u<<10) + +static struct commit *interesting(struct commit_list *list) +{ + while (list) { + struct commit *commit = list->item; + list = list->next; + if (commit->object.flags & STALE) + continue; + return commit; + } + return NULL; +} + +/* + * A pathological example of how this thing works. + * + * Suppose we had this commit graph, where chronologically + * the timestamp on the commit are A <= B <= C <= D <= E <= F + * and we are trying to figure out the merge base for E and F + * commits. + * + * F + * / \ + * E A D + * \ / / + * B / + * \ / + * C + * + * First we push E and F to list to be processed. E gets bit 1 + * and F gets bit 2. The list becomes: + * + * list=F(2) E(1), result=empty + * + * Then we pop F, the newest commit, from the list. Its flag is 2. + * We scan its parents, mark them reachable from the side that F is + * reachable from, and push them to the list: + * + * list=E(1) D(2) A(2), result=empty + * + * Next pop E and do the same. + * + * list=D(2) B(1) A(2), result=empty + * + * Next pop D and do the same. + * + * list=C(2) B(1) A(2), result=empty + * + * Next pop C and do the same. + * + * list=B(1) A(2), result=empty + * + * Now it is B's turn. We mark its parent, C, reachable from B's side, + * and push it to the list: + * + * list=C(3) A(2), result=empty + * + * Now pop C and notice it has flags==3. It is placed on the result list, + * and the list now contains: + * + * list=A(2), result=C(3) + * + * We pop A and do the same. + * + * list=B(3), result=C(3) + * + * Next, we pop B and something very interesting happens. It has flags==3 + * so it is also placed on the result list, and its parents are marked + * stale, retroactively, and placed back on the list: + * + * list=C(7), result=C(7) B(3) + * + * Now, list does not have any interesting commit. So we find the newest + * commit from the result list that is not marked stale. Which is + * commit B. + * + * + * Another pathological example how this thing used to fail to mark an + * ancestor of a merge base as STALE before we introduced the + * postprocessing phase (mark_reachable_commits). + * + * 2 + * H + * 1 / \ + * G A \ + * |\ / \ + * | B \ + * | \ \ + * \ C F + * \ \ / + * \ D / + * \ | / + * \| / + * E + * + * list A B C D E F G H + * G1 H2 - - - - - - 1 2 + * H2 E1 B1 - 1 - - 1 - 1 2 + * F2 E1 B1 A2 2 1 - - 1 2 1 2 + * E3 B1 A2 2 1 - - 3 2 1 2 + * B1 A2 2 1 - - 3 2 1 2 + * C1 A2 2 1 1 - 3 2 1 2 + * D1 A2 2 1 1 1 3 2 1 2 + * A2 2 1 1 1 3 2 1 2 + * B3 2 3 1 1 3 2 1 2 + * C7 2 3 7 1 3 2 1 2 + * + * At this point, unfortunately, everybody in the list is + * stale, so we fail to complete the following two + * steps to fully marking stale commits. + * + * D7 2 3 7 7 3 2 1 2 + * E7 2 3 7 7 7 2 1 2 + * + * and we ended up showing E as an interesting merge base. + * The postprocessing phase re-injects C and continues traversal + * to contaminate D and E. + */ + +static void mark_reachable_commits(struct commit_list *result, + struct commit_list *list) +{ + struct commit_list *tmp; + + /* + * Postprocess to fully contaminate the well. + */ + for (tmp = result; tmp; tmp = tmp->next) { + struct commit *c = tmp->item; + /* Reinject stale ones to list, + * so we can scan their parents. + */ + if (c->object.flags & STALE) + commit_list_insert(c, &list); + } + while (list) { + struct commit *c = list->item; + struct commit_list *parents; + + tmp = list; + list = list->next; + free(tmp); + + /* Anything taken out of the list is stale, so + * mark all its parents stale. We do not + * parse new ones (we already parsed all the relevant + * ones). + */ + parents = c->parents; + while (parents) { + struct commit *p = parents->item; + parents = parents->next; + if (!(p->object.flags & STALE)) { + p->object.flags |= STALE; + commit_list_insert(p, &list); + } + } + } +} + +struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, + int cleanup) +{ + struct commit_list *list = NULL; + struct commit_list *result = NULL; + struct commit_list *tmp = NULL; + + if (rev1 == rev2) + return commit_list_insert(rev1, &result); + + parse_commit(rev1); + parse_commit(rev2); + + rev1->object.flags |= PARENT1; + rev2->object.flags |= PARENT2; + insert_by_date(rev1, &list); + insert_by_date(rev2, &list); + + while (interesting(list)) { + struct commit *commit = list->item; + struct commit_list *parents; + int flags = commit->object.flags + & (PARENT1 | PARENT2 | STALE); + + tmp = list; + list = list->next; + free(tmp); + if (flags == (PARENT1 | PARENT2)) { + insert_by_date(commit, &result); + + /* Mark parents of a found merge stale */ + flags |= STALE; + } + parents = commit->parents; + while (parents) { + struct commit *p = parents->item; + parents = parents->next; + if ((p->object.flags & flags) == flags) + continue; + parse_commit(p); + p->object.flags |= flags; + insert_by_date(p, &list); + } + } + + if (!result) + goto finish; + + if (result->next && list) + mark_reachable_commits(result, list); + + /* cull duplicates */ + for (tmp = result, list = NULL; tmp; ) { + struct commit *commit = tmp->item; + struct commit_list *next = tmp->next; + if (commit->object.flags & STALE) { + if (list != NULL) + list->next = next; + free(tmp); + } else { + if (list == NULL) + result = tmp; + list = tmp; + commit->object.flags |= STALE; + } + + tmp = next; + } + + finish: + if (cleanup) { + clear_commit_marks(rev1, PARENT1 | PARENT2 | STALE); + clear_commit_marks(rev2, PARENT1 | PARENT2 | STALE); + } + + return result; +} @@ -105,4 +105,6 @@ struct commit_graft *read_graft_line(char *buf, int len); int register_commit_graft(struct commit_graft *, int); int read_graft_file(const char *graft_file); +extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); + #endif /* COMMIT_H */ diff --git a/contrib/git-svn/.gitignore b/contrib/git-svn/.gitignore deleted file mode 100644 index d8d87e3af9..0000000000 --- a/contrib/git-svn/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -git-svn -git-svn.xml -git-svn.html -git-svn.1 diff --git a/contrib/git-svn/Makefile b/contrib/git-svn/Makefile deleted file mode 100644 index 7c20946943..0000000000 --- a/contrib/git-svn/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -all: git-svn - -prefix?=$(HOME) -bindir=$(prefix)/bin -mandir=$(prefix)/man -man1=$(mandir)/man1 -INSTALL?=install -doc_conf=../../Documentation/asciidoc.conf --include ../../config.mak - -git-svn: git-svn.perl - cp $< $@ - chmod +x $@ - -install: all - $(INSTALL) -d -m755 $(DESTDIR)$(bindir) - $(INSTALL) git-svn $(DESTDIR)$(bindir) - -install-doc: doc - $(INSTALL) git-svn.1 $(DESTDIR)$(man1) - -doc: git-svn.1 -git-svn.1 : git-svn.xml - xmlto man git-svn.xml -git-svn.xml : git-svn.txt - asciidoc -b docbook -d manpage \ - -f ../../Documentation/asciidoc.conf $< -git-svn.html : git-svn.txt - asciidoc -b xhtml11 -d manpage \ - -f ../../Documentation/asciidoc.conf $< -test: git-svn - cd t && for i in t????-*.sh; do $(SHELL) ./$$i $(TEST_FLAGS); done - -# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL -full-test: - $(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C - $(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C - $(MAKE) test GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ - LC_ALL=en_US.UTF-8 - $(MAKE) test GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ - LC_ALL=en_US.UTF-8 - -clean: - rm -f git-svn *.xml *.html *.1 diff --git a/contrib/git-svn/t/lib-git-svn.sh b/contrib/git-svn/t/lib-git-svn.sh deleted file mode 100644 index d7f972a0c8..0000000000 --- a/contrib/git-svn/t/lib-git-svn.sh +++ /dev/null @@ -1,45 +0,0 @@ -PATH=$PWD/../:$PATH -if test -d ../../../t -then - cd ../../../t -else - echo "Must be run in contrib/git-svn/t" >&2 - exit 1 -fi - -. ./test-lib.sh - -GIT_DIR=$PWD/.git -GIT_SVN_DIR=$GIT_DIR/svn/git-svn -SVN_TREE=$GIT_SVN_DIR/svn-tree - -svnadmin >/dev/null 2>&1 -if test $? != 1 -then - test_expect_success 'skipping contrib/git-svn test' : - test_done - exit -fi - -svn >/dev/null 2>&1 -if test $? != 1 -then - test_expect_success 'skipping contrib/git-svn test' : - test_done - exit -fi - -svnrepo=$PWD/svnrepo - -set -e - -if svnadmin create --help | grep fs-type >/dev/null -then - svnadmin create --fs-type fsfs "$svnrepo" -else - svnadmin create "$svnrepo" -fi - -svnrepo="file://$svnrepo/test-git-svn" - - @@ -13,6 +13,7 @@ static int use_size_cache; +static int diff_detect_rename_default = 0; static int diff_rename_limit_default = -1; static int diff_use_color_default = 0; @@ -101,7 +102,13 @@ static const char *parse_diff_color_value(const char *value, const char *var) die("bad config value '%s' for variable '%s'", value, var); } -int git_diff_config(const char *var, const char *value) +/* + * These are to give UI layer defaults. + * The core-level commands such as git-diff-files should + * never be affected by the setting of diff.renames + * the user happens to have in the configuration file. + */ +int git_diff_ui_config(const char *var, const char *value) { if (!strcmp(var, "diff.renamelimit")) { diff_rename_limit_default = git_config_int(var, value); @@ -110,8 +117,14 @@ int git_diff_config(const char *var, const char *value) if (!strcmp(var, "diff.color")) { if (!value) diff_use_color_default = 1; /* bool */ - else if (!strcasecmp(value, "auto")) - diff_use_color_default = isatty(1); + else if (!strcasecmp(value, "auto")) { + diff_use_color_default = 0; + if (isatty(1) || pager_in_use) { + char *term = getenv("TERM"); + if (term && strcmp(term, "dumb")) + diff_use_color_default = 1; + } + } else if (!strcasecmp(value, "never")) diff_use_color_default = 0; else if (!strcasecmp(value, "always")) @@ -120,6 +133,16 @@ int git_diff_config(const char *var, const char *value) diff_use_color_default = git_config_bool(var, value); return 0; } + if (!strcmp(var, "diff.renames")) { + if (!value) + diff_detect_rename_default = DIFF_DETECT_RENAME; + else if (!strcasecmp(value, "copies") || + !strcasecmp(value, "copy")) + diff_detect_rename_default = DIFF_DETECT_COPY; + else if (git_config_bool(var,value)) + diff_detect_rename_default = DIFF_DETECT_RENAME; + return 0; + } if (!strncmp(var, "diff.color.", 11)) { int slot = parse_diff_color_slot(var, 11); diff_colors[slot] = parse_diff_color_value(value, var); @@ -329,7 +352,9 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) } if (len > 0 && line[len-1] == '\n') len--; - printf("%s%.*s%s\n", set, (int) len, line, reset); + fputs (set, stdout); + fwrite (line, len, 1, stdout); + puts (reset); } static char *pprint_rename(const char *a, const char *b) @@ -721,7 +746,7 @@ static void builtin_diff(const char *name_a, if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0) die("unable to read files to diff"); - if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) { + if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) { /* Quite common confusing case */ if (mf1.size == mf2.size && !memcmp(mf1.ptr, mf2.ptr, mf1.size)) @@ -1429,6 +1454,7 @@ void diff_setup(struct diff_options *options) options->change = diff_change; options->add_remove = diff_addremove; options->color_diff = diff_use_color_default; + options->detect_rename = diff_detect_rename_default; } int diff_setup_done(struct diff_options *options) @@ -1559,6 +1585,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->output_format |= DIFF_FORMAT_PATCH; options->full_index = options->binary = 1; } + else if (!strcmp(arg, "-a") || !strcmp(arg, "--text")) { + options->text = 1; + } else if (!strcmp(arg, "--name-only")) options->output_format |= DIFF_FORMAT_NAME; else if (!strcmp(arg, "--name-status")) @@ -1608,10 +1637,14 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) } else if (!strcmp(arg, "--color")) options->color_diff = 1; + else if (!strcmp(arg, "--no-color")) + options->color_diff = 0; else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) options->xdl_opts |= XDF_IGNORE_WHITESPACE; else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(arg, "--no-renames")) + options->detect_rename = 0; else return 0; return 1; @@ -42,6 +42,7 @@ struct diff_options { unsigned recursive:1, tree_in_recursive:1, binary:1, + text:1, full_index:1, silent_on_remove:1, find_copies_harder:1, @@ -122,7 +123,7 @@ extern int diff_scoreopt_parse(const char *opt); #define DIFF_SETUP_USE_CACHE 2 #define DIFF_SETUP_USE_SIZE_CACHE 4 -extern int git_diff_config(const char *var, const char *value); +extern int git_diff_ui_config(const char *var, const char *value); extern void diff_setup(struct diff_options *); extern int diff_opt_parse(struct diff_options *, const char **, int); extern int diff_setup_done(struct diff_options *); @@ -161,7 +162,8 @@ extern void diffcore_std_no_resolve(struct diff_options *); " -O<file> reorder diffs according to the <file>.\n" \ " -S<string> find filepair whose only one side contains the string.\n" \ " --pickaxe-all\n" \ -" show all files diff when -S is used and hit is found.\n" +" show all files diff when -S is used and hit is found.\n" \ +" -a --text treat all files as text.\n" extern int diff_queue_is_empty(void); extern void diff_flush(struct diff_options*); diff --git a/git-bisect.sh b/git-bisect.sh index 03df1433ef..06a8d26945 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -13,7 +13,7 @@ git bisect log show bisect log.' . git-sh-setup sq() { - perl -e ' + @@PERL@@ -e ' for (@ARGV) { s/'\''/'\'\\\\\'\''/g; print " '\''$_'\''"; diff --git a/git-clone.sh b/git-clone.sh index 6a14b25911..0368803883 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -324,7 +324,7 @@ test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp" if test -f "$GIT_DIR/CLONE_HEAD" then # Read git-fetch-pack -k output and store the remote branches. - perl -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin" + @@PERL@@ -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin" fi cd "$D" || exit diff --git a/git-commit.sh b/git-commit.sh index 22c4ce86c3..08d786db2f 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -147,7 +147,7 @@ run_status () { git-ls-files -z --others $option \ --exclude-per-directory=.gitignore fi | - perl -e '$/ = "\0"; + @@PERL@@ -e '$/ = "\0"; my $shown = 0; while (<>) { chomp; diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index d1051d074b..5dcb2f9a8e 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -63,15 +63,15 @@ foreach my $p (@commit) { } if ($parent) { + my $found; # double check that it's a valid parent foreach my $p (@parents) { - my $found; if ($p eq $parent) { $found = 1; last; }; # found it - die "Did not find $parent in the parents for this commit!"; } + die "Did not find $parent in the parents for this commit!" if !$found; } else { # we don't have a parent from the cmdline... if (@parents == 1) { # it's safe to get it from the commit $parent = $parents[0]; diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 5ccca4f99f..c30ef70427 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -779,7 +779,7 @@ sub req_update #$log->debug("update state : " . Dumper($state)); - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { $filename = filecleanup($filename); @@ -1031,7 +1031,7 @@ sub req_ci my @committedfiles = (); - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { my $committedfile = $filename; @@ -1145,7 +1145,7 @@ sub req_ci $updater->update(); - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @committedfiles ) { $filename = filecleanup($filename); @@ -1190,7 +1190,7 @@ sub req_status # if no files were specified, we need to work out what files we should be providing status on ... argsfromdir($updater); - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { $filename = filecleanup($filename); @@ -1291,7 +1291,7 @@ sub req_diff # if no files were specified, we need to work out what files we should be providing status on ... argsfromdir($updater); - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { $filename = filecleanup($filename); @@ -1433,7 +1433,7 @@ sub req_log # if no files were specified, we need to work out what files we should be providing status on ... argsfromdir($updater); - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { $filename = filecleanup($filename); @@ -1519,7 +1519,7 @@ sub req_annotate chdir $tmpdir; - # foreach file specified on the commandline ... + # foreach file specified on the command line ... foreach my $filename ( @{$state->{args}} ) { $filename = filecleanup($filename); diff --git a/git-fetch.sh b/git-fetch.sh index 48818f8224..f80299daaa 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -278,7 +278,7 @@ fetch_main () { head="ref: $remote_name" while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null do - remote_name_quoted=$(perl -e ' + remote_name_quoted=$(@@PERL@@ -e ' my $u = $ARGV[0]; $u =~ s/^ref:\s*//; $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg; diff --git a/git-rebase.sh b/git-rebase.sh index 3945e06714..1b9e986926 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -311,7 +311,7 @@ echo "$prev_head" > "$dotest/prev_head" msgnum=0 for cmt in `git-rev-list --no-merges "$upstream"..ORIG_HEAD \ - | perl -e 'print reverse <>'` + | @@PERL@@ -e 'print reverse <>'` do msgnum=$(($msgnum + 1)) echo "$cmt" > "$dotest/cmt.$msgnum" diff --git a/git-send-email.perl b/git-send-email.perl index b04b8f40e9..c9c1975b7f 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -34,8 +34,43 @@ sub readline { package main; # most mail servers generate the Date: header, but not all... -$ENV{LC_ALL} = 'C'; -use POSIX qw/strftime/; +sub format_2822_time { + my ($time) = @_; + my @localtm = localtime($time); + my @gmttm = gmtime($time); + my $localmin = $localtm[1] + $localtm[2] * 60; + my $gmtmin = $gmttm[1] + $gmttm[2] * 60; + if ($localtm[0] != $gmttm[0]) { + die "local zone differs from GMT by a non-minute interval\n"; + } + if ((($gmttm[6] + 1) % 7) == $localtm[6]) { + $localmin += 1440; + } elsif ((($gmttm[6] - 1) % 7) == $localtm[6]) { + $localmin -= 1440; + } elsif ($gmttm[6] != $localtm[6]) { + die "local time offset greater than or equal to 24 hours\n"; + } + my $offset = $localmin - $gmtmin; + my $offhour = $offset / 60; + my $offmin = abs($offset % 60); + if (abs($offhour) >= 24) { + die ("local time offset greater than or equal to 24 hours\n"); + } + + return sprintf("%s, %2d %s %d %02d:%02d:%02d %s%02d%02d", + qw(Sun Mon Tue Wed Thu Fri Sat)[$localtm[6]], + $localtm[3], + qw(Jan Feb Mar Apr May Jun + Jul Aug Sep Oct Nov Dec)[$localtm[4]], + $localtm[5]+1900, + $localtm[2], + $localtm[1], + $localtm[0], + ($offset >= 0) ? '+' : '-', + abs($offhour), + $offmin, + ); +} my $have_email_valid = eval { require Email::Valid; 1 }; my $smtp; @@ -387,7 +422,7 @@ sub send_message my @recipients = unique_email_list(@to); my $to = join (",\n\t", @recipients); @recipients = unique_email_list(@recipients,@cc,@bcclist); - my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++)); + my $date = format_2822_time($time++); my $gitversion = '@@GIT_VERSION@@'; if ($gitversion =~ m/..GIT_VERSION../) { $gitversion = `git --version`; diff --git a/contrib/git-svn/git-svn.perl b/git-svn.perl index 8bc4188e03..145eaa865a 100755 --- a/contrib/git-svn/git-svn.perl +++ b/git-svn.perl @@ -8,7 +8,7 @@ use vars qw/ $AUTHOR $VERSION $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $GIT_SVN_DIR $REVDB/; $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; -$VERSION = '1.1.1-broken'; +$VERSION = '@@GIT_VERSION@@'; use Cwd qw/abs_path/; $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git'); @@ -251,6 +251,11 @@ int main(int argc, const char **argv, char **envp) cmd = *++argv; argc--; + if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { + setup_pager(); + continue; + } + if (strncmp(cmd, "--", 2)) break; diff --git a/merge-base.c b/merge-base.c index 4856ca01c3..59f723f404 100644 --- a/merge-base.c +++ b/merge-base.c @@ -2,232 +2,22 @@ #include "cache.h" #include "commit.h" -#define PARENT1 1 -#define PARENT2 2 -#define UNINTERESTING 4 - -static struct commit *interesting(struct commit_list *list) -{ - while (list) { - struct commit *commit = list->item; - list = list->next; - if (commit->object.flags & UNINTERESTING) - continue; - return commit; - } - return NULL; -} - -/* - * A pathological example of how this thing works. - * - * Suppose we had this commit graph, where chronologically - * the timestamp on the commit are A <= B <= C <= D <= E <= F - * and we are trying to figure out the merge base for E and F - * commits. - * - * F - * / \ - * E A D - * \ / / - * B / - * \ / - * C - * - * First we push E and F to list to be processed. E gets bit 1 - * and F gets bit 2. The list becomes: - * - * list=F(2) E(1), result=empty - * - * Then we pop F, the newest commit, from the list. Its flag is 2. - * We scan its parents, mark them reachable from the side that F is - * reachable from, and push them to the list: - * - * list=E(1) D(2) A(2), result=empty - * - * Next pop E and do the same. - * - * list=D(2) B(1) A(2), result=empty - * - * Next pop D and do the same. - * - * list=C(2) B(1) A(2), result=empty - * - * Next pop C and do the same. - * - * list=B(1) A(2), result=empty - * - * Now it is B's turn. We mark its parent, C, reachable from B's side, - * and push it to the list: - * - * list=C(3) A(2), result=empty - * - * Now pop C and notice it has flags==3. It is placed on the result list, - * and the list now contains: - * - * list=A(2), result=C(3) - * - * We pop A and do the same. - * - * list=B(3), result=C(3) - * - * Next, we pop B and something very interesting happens. It has flags==3 - * so it is also placed on the result list, and its parents are marked - * uninteresting, retroactively, and placed back on the list: - * - * list=C(7), result=C(7) B(3) - * - * Now, list does not have any interesting commit. So we find the newest - * commit from the result list that is not marked uninteresting. Which is - * commit B. - * - * - * Another pathological example how this thing used to fail to mark an - * ancestor of a merge base as UNINTERESTING before we introduced the - * postprocessing phase (mark_reachable_commits). - * - * 2 - * H - * 1 / \ - * G A \ - * |\ / \ - * | B \ - * | \ \ - * \ C F - * \ \ / - * \ D / - * \ | / - * \| / - * E - * - * list A B C D E F G H - * G1 H2 - - - - - - 1 2 - * H2 E1 B1 - 1 - - 1 - 1 2 - * F2 E1 B1 A2 2 1 - - 1 2 1 2 - * E3 B1 A2 2 1 - - 3 2 1 2 - * B1 A2 2 1 - - 3 2 1 2 - * C1 A2 2 1 1 - 3 2 1 2 - * D1 A2 2 1 1 1 3 2 1 2 - * A2 2 1 1 1 3 2 1 2 - * B3 2 3 1 1 3 2 1 2 - * C7 2 3 7 1 3 2 1 2 - * - * At this point, unfortunately, everybody in the list is - * uninteresting, so we fail to complete the following two - * steps to fully marking uninteresting commits. - * - * D7 2 3 7 7 3 2 1 2 - * E7 2 3 7 7 7 2 1 2 - * - * and we ended up showing E as an interesting merge base. - * The postprocessing phase re-injects C and continues traversal - * to contaminate D and E. - */ - static int show_all = 0; -static void mark_reachable_commits(struct commit_list *result, - struct commit_list *list) -{ - struct commit_list *tmp; - - /* - * Postprocess to fully contaminate the well. - */ - for (tmp = result; tmp; tmp = tmp->next) { - struct commit *c = tmp->item; - /* Reinject uninteresting ones to list, - * so we can scan their parents. - */ - if (c->object.flags & UNINTERESTING) - commit_list_insert(c, &list); - } - while (list) { - struct commit *c = list->item; - struct commit_list *parents; - - tmp = list; - list = list->next; - free(tmp); - - /* Anything taken out of the list is uninteresting, so - * mark all its parents uninteresting. We do not - * parse new ones (we already parsed all the relevant - * ones). - */ - parents = c->parents; - while (parents) { - struct commit *p = parents->item; - parents = parents->next; - if (!(p->object.flags & UNINTERESTING)) { - p->object.flags |= UNINTERESTING; - commit_list_insert(p, &list); - } - } - } -} - static int merge_base(struct commit *rev1, struct commit *rev2) { - struct commit_list *list = NULL; - struct commit_list *result = NULL; - struct commit_list *tmp = NULL; - - if (rev1 == rev2) { - printf("%s\n", sha1_to_hex(rev1->object.sha1)); - return 0; - } - - parse_commit(rev1); - parse_commit(rev2); - - rev1->object.flags |= 1; - rev2->object.flags |= 2; - insert_by_date(rev1, &list); - insert_by_date(rev2, &list); - - while (interesting(list)) { - struct commit *commit = list->item; - struct commit_list *parents; - int flags = commit->object.flags & 7; - - tmp = list; - list = list->next; - free(tmp); - if (flags == 3) { - insert_by_date(commit, &result); - - /* Mark parents of a found merge uninteresting */ - flags |= UNINTERESTING; - } - parents = commit->parents; - while (parents) { - struct commit *p = parents->item; - parents = parents->next; - if ((p->object.flags & flags) == flags) - continue; - parse_commit(p); - p->object.flags |= flags; - insert_by_date(p, &list); - } - } + struct commit_list *result = get_merge_bases(rev1, rev2, 0); if (!result) return 1; - if (result->next && list) - mark_reachable_commits(result, list); - while (result) { - struct commit *commit = result->item; - result = result->next; - if (commit->object.flags & UNINTERESTING) - continue; - printf("%s\n", sha1_to_hex(commit->object.sha1)); + printf("%s\n", sha1_to_hex(result->item->object.sha1)); if (!show_all) return 0; - commit->object.flags |= UNINTERESTING; + result = result->next; } + return 0; } @@ -5,6 +5,8 @@ * something different on Windows, for example. */ +int pager_in_use; + static void run_pager(const char *pager) { execlp(pager, pager, NULL); @@ -24,6 +26,8 @@ void setup_pager(void) else if (!*pager || !strcmp(pager, "cat")) return; + pager_in_use = 1; /* means we are emitting to terminal */ + if (pipe(fd) < 0) return; pid = fork(); @@ -362,7 +362,7 @@ static int log_ref_write(struct ref_lock *lock, int logfd, written, oflags = O_APPEND | O_WRONLY; unsigned maxlen, len; char *logrec; - const char *comitter; + const char *committer; if (log_all_ref_updates) { if (safe_create_leading_directories(lock->log_file) < 0) @@ -380,23 +380,23 @@ static int log_ref_write(struct ref_lock *lock, } setup_ident(); - comitter = git_committer_info(1); + committer = git_committer_info(1); if (msg) { - maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5; + maxlen = strlen(committer) + strlen(msg) + 2*40 + 5; logrec = xmalloc(maxlen); len = snprintf(logrec, maxlen, "%s %s %s\t%s\n", sha1_to_hex(lock->old_sha1), sha1_to_hex(sha1), - comitter, + committer, msg); } else { - maxlen = strlen(comitter) + 2*40 + 4; + maxlen = strlen(committer) + 2*40 + 4; logrec = xmalloc(maxlen); len = snprintf(logrec, maxlen, "%s %s %s\n", sha1_to_hex(lock->old_sha1), sha1_to_hex(sha1), - comitter); + committer); } written = len <= maxlen ? write(logfd, logrec, len) : -1; free(logrec); diff --git a/revision.c b/revision.c index ab89c22417..a7750e626b 100644 --- a/revision.c +++ b/revision.c @@ -537,6 +537,18 @@ void init_revisions(struct rev_info *revs) diff_setup(&revs->diffopt); } +static void add_pending_commit_list(struct rev_info *revs, + struct commit_list *commit_list, + unsigned int flags) +{ + while (commit_list) { + struct object *object = &commit_list->item->object; + object->flags |= flags; + add_pending_object(revs, object, sha1_to_hex(object->sha1)); + commit_list = commit_list->next; + } +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -772,27 +784,46 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch unsigned char from_sha1[20]; const char *next = dotdot + 2; const char *this = arg; + int symmetric = *next == '.'; + unsigned int flags_exclude = flags ^ UNINTERESTING; + *dotdot = 0; + next += symmetric; + if (!*next) next = "HEAD"; if (dotdot == arg) this = "HEAD"; if (!get_sha1(this, from_sha1) && !get_sha1(next, sha1)) { - struct object *exclude; - struct object *include; - - exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING); - include = get_reference(revs, next, sha1, flags); - if (!exclude || !include) - die("Invalid revision range %s..%s", arg, next); + struct commit *a, *b; + struct commit_list *exclude; + + a = lookup_commit_reference(from_sha1); + b = lookup_commit_reference(sha1); + if (!a || !b) { + die(symmetric ? + "Invalid symmetric difference expression %s...%s" : + "Invalid revision range %s..%s", + arg, next); + } if (!seen_dashdash) { *dotdot = '.'; verify_non_filename(revs->prefix, arg); } - add_pending_object(revs, exclude, this); - add_pending_object(revs, include, next); + + if (symmetric) { + exclude = get_merge_bases(a, b, 1); + add_pending_commit_list(revs, exclude, + flags_exclude); + free_commit_list(exclude); + a->object.flags |= flags; + } else + a->object.flags |= flags_exclude; + b->object.flags |= flags; + add_pending_object(revs, &a->object, this); + add_pending_object(revs, &b->object, next); continue; } *dotdot = '.'; diff --git a/server-info.c b/server-info.c index 0eb5132cc1..fdfe05a2da 100644 --- a/server-info.c +++ b/server-info.c @@ -94,7 +94,7 @@ static int read_pack_info_file(const char *infofile) fp = fopen(infofile, "r"); if (!fp) - return 1; /* nonexisting is not an error. */ + return 1; /* nonexistent is not an error. */ while (fgets(line, sizeof(line), fp)) { int len = strlen(line); diff --git a/t/Makefile b/t/Makefile index 632c55f6d5..89835093fb 100644 --- a/t/Makefile +++ b/t/Makefile @@ -11,6 +11,7 @@ TAR ?= $(TAR) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) +TSVN = $(wildcard t91[0-9][0-9]-*.sh) ifdef NO_PYTHON GIT_TEST_OPTS += --no-python @@ -24,6 +25,15 @@ $(T): clean: rm -fr trash +# we can test NO_OPTIMIZE_COMMITS independently of LC_ALL +full-svn-test: + $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C + $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C + $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ + LC_ALL=en_US.UTF-8 + $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \ + LC_ALL=en_US.UTF-8 + .PHONY: $(T) clean .NOTPARALLEL: diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh new file mode 100644 index 0000000000..29a1e72c61 --- /dev/null +++ b/t/lib-git-svn.sh @@ -0,0 +1,50 @@ +. ./test-lib.sh + +if test -n "$NO_SVN_TESTS" +then + test_expect_success 'skipping git-svn tests, NO_SVN_TESTS defined' : + test_done + exit +fi + +GIT_DIR=$PWD/.git +GIT_SVN_DIR=$GIT_DIR/svn/git-svn +SVN_TREE=$GIT_SVN_DIR/svn-tree + +perl -e 'use SVN::Core' >/dev/null 2>&1 +if test $? -ne 0 +then + echo 'Perl SVN libraries not found, tests requiring those will be skipped' + GIT_SVN_NO_LIB=1 +fi + +svnadmin >/dev/null 2>&1 +if test $? -ne 1 +then + test_expect_success 'skipping git-svn tests, svnadmin not found' : + test_done + exit +fi + +svn >/dev/null 2>&1 +if test $? -ne 1 +then + test_expect_success 'skipping git-svn tests, svn not found' : + test_done + exit +fi + +svnrepo=$PWD/svnrepo + +set -e + +if svnadmin create --help | grep fs-type >/dev/null +then + svnadmin create --fs-type fsfs "$svnrepo" +else + svnadmin create "$svnrepo" +fi + +svnrepo="file://$svnrepo/test-git-svn" + + diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/t/t9100-git-svn-basic.sh index b482bb64c0..bf1d6381d9 100644..100755 --- a/contrib/git-svn/t/t0000-contrib-git-svn.sh +++ b/t/t9100-git-svn-basic.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Eric Wong # -test_description='git-svn tests' +test_description='git-svn basic tests' GIT_SVN_LC_ALL=$LC_ALL case "$LC_ALL" in @@ -17,6 +17,8 @@ esac . ./lib-git-svn.sh +echo 'define NO_SVN_TESTS to skip git-svn tests' + mkdir import cd import diff --git a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh b/t/t9101-git-svn-props.sh index a5a235f100..a5a235f100 100644..100755 --- a/contrib/git-svn/t/t0001-contrib-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh diff --git a/contrib/git-svn/t/t0002-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh index d693d183c8..d693d183c8 100644..100755 --- a/contrib/git-svn/t/t0002-deep-rmdir.sh +++ b/t/t9102-git-svn-deep-rmdir.sh diff --git a/contrib/git-svn/t/t0003-graft-branches.sh b/t/t9103-git-svn-graft-branches.sh index cc62d4ece8..cc62d4ece8 100644..100755 --- a/contrib/git-svn/t/t0003-graft-branches.sh +++ b/t/t9103-git-svn-graft-branches.sh diff --git a/contrib/git-svn/t/t0004-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index 01488ff78a..01488ff78a 100644..100755 --- a/contrib/git-svn/t/t0004-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh diff --git a/contrib/git-svn/t/t0005-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index f994b72f80..f994b72f80 100644..100755 --- a/contrib/git-svn/t/t0005-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh diff --git a/templates/hooks--update b/templates/hooks--update index d7a8f0a849..76d5ac2477 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -60,7 +60,7 @@ then echo "Changes since $prev:" git rev-list --pretty $prev..$3 | $short echo --- - git diff $prev..$3 | diffstat -p1 + git diff --stat $prev..$3 echo --- fi ;; @@ -75,7 +75,7 @@ else base=$(git-merge-base "$2" "$3") case "$base" in "$2") - git diff "$3" "^$base" | diffstat -p1 + git diff --stat "$3" "^$base" echo echo "New commits:" ;; |