diff options
335 files changed, 10803 insertions, 3096 deletions
diff --git a/.gitignore b/.gitignore index d9adce585a..1c57d4c958 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 f628c1f3b7..0d7fa9cca9 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/Makefile b/Documentation/Makefile index c34c1cae20..144ec32f12 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -32,6 +32,7 @@ DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT)) prefix?=$(HOME) bindir?=$(prefix)/bin htmldir?=$(prefix)/share/doc/git-doc +pdfdir?=$(prefix)/share/doc/git-doc mandir?=$(prefix)/share/man man1dir=$(mandir)/man1 man5dir=$(mandir)/man5 @@ -50,6 +51,7 @@ infodir?=$(prefix)/share/info MAKEINFO=makeinfo INSTALL_INFO=install-info DOCBOOK2X_TEXI=docbook2x-texi +DBLATEX=dblatex ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif @@ -87,6 +89,8 @@ man7: $(DOC_MAN7) info: git.info gitman.info +pdf: user-manual.pdf + install: install-man install-man: man @@ -107,6 +111,10 @@ install-info: info echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \ fi +install-pdf: pdf + $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir) + $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir) + install-html: html sh ./install-webdoc.sh $(DESTDIR)$(htmldir) @@ -187,17 +195,23 @@ git.info: user-manual.texi user-manual.texi: user-manual.xml $(RM) $@+ $@ - $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout | $(PERL_PATH) fix-texi.perl >$@+ + $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout | \ + $(PERL_PATH) fix-texi.perl >$@+ + mv $@+ $@ + +user-manual.pdf: user-manual.xml + $(RM) $@+ $@ + $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< mv $@+ $@ gitman.texi: $(MAN_XML) cat-texi.perl $(RM) $@+ $@ - ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --to-stdout $(xml);)) | \ - $(PERL_PATH) cat-texi.perl $@ >$@+ + ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \ + --to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+ mv $@+ $@ gitman.info: gitman.texi - $(MAKEINFO) --no-split $*.texi + $(MAKEINFO) --no-split --no-validate $*.texi $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml $(RM) $@+ $@ diff --git a/Documentation/RelNotes-1.5.2.2.txt b/Documentation/RelNotes-1.5.2.2.txt index f6393f8a94..7bfa341750 100644 --- a/Documentation/RelNotes-1.5.2.2.txt +++ b/Documentation/RelNotes-1.5.2.2.txt @@ -45,7 +45,7 @@ Fixes since v1.5.2.1 correctly when the branch name had slash in it. - The email address of the user specified with user.email - configuration was overriden by EMAIL environment variable. + configuration was overridden by EMAIL environment variable. - The tree parser did not warn about tree entries with nonsense file modes, and assumed they must be blobs. diff --git a/Documentation/RelNotes-1.6.0.2.txt b/Documentation/RelNotes-1.6.0.2.txt index 7a9646fc4f..51b32f5d94 100644 --- a/Documentation/RelNotes-1.6.0.2.txt +++ b/Documentation/RelNotes-1.6.0.2.txt @@ -7,7 +7,7 @@ Fixes since v1.6.0.1 * Installation on platforms that needs .exe suffix to git-* programs were broken in 1.6.0.1. -* Installation on filesystems without symbolic links support did nto +* Installation on filesystems without symbolic links support did not work well. * In-tree documentations and test scripts now use "git foo" form to set a diff --git a/Documentation/RelNotes-1.6.1.1.txt b/Documentation/RelNotes-1.6.1.1.txt index 88454c1973..8c594ba02f 100644 --- a/Documentation/RelNotes-1.6.1.1.txt +++ b/Documentation/RelNotes-1.6.1.1.txt @@ -41,11 +41,11 @@ Fixes since v1.6.1 work tree upon delete/modify conflict. * "git merge -s recursive" didn't leave the index unmerged for entries with - rename/delete conflictd. + rename/delete conflicts. * "git merge -s recursive" clobbered untracked files in the work tree. -* "git mv -k" with more than one errorneous paths misbehaved. +* "git mv -k" with more than one erroneous paths misbehaved. * "git read-tree -m -u" hence branch switching incorrectly lost a subdirectory in rare cases. diff --git a/Documentation/RelNotes-1.6.1.2.txt b/Documentation/RelNotes-1.6.1.2.txt index 230aa3d8e8..be37cbb858 100644 --- a/Documentation/RelNotes-1.6.1.2.txt +++ b/Documentation/RelNotes-1.6.1.2.txt @@ -4,8 +4,8 @@ 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 +* The logic for rename detection in internal diff used by commands like + "git diff" and "git blame" has 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 diff --git a/Documentation/RelNotes-1.6.2.1.txt b/Documentation/RelNotes-1.6.2.1.txt new file mode 100644 index 0000000000..dfa36416af --- /dev/null +++ b/Documentation/RelNotes-1.6.2.1.txt @@ -0,0 +1,19 @@ +GIT v1.6.2.1 Release Notes +========================== + +Fixes since v1.6.2 +------------------ + +* .gitignore learned to handle backslash as a quoting mechanism for + comment introduction character "#". + +* timestamp output in --date=relative mode used to display timestamps that + are long time ago in the default mode; it now uses "N years M months + ago", and "N years ago". + +* git-add -i/-p now works with non-ASCII pathnames. + +* "git hash-object -w" did not read from the configuration file from the + correct .git directory. + +* git-send-email learned to correctly handle multiple Cc: addresses. diff --git a/Documentation/RelNotes-1.6.2.2.txt b/Documentation/RelNotes-1.6.2.2.txt new file mode 100644 index 0000000000..fafa9986b0 --- /dev/null +++ b/Documentation/RelNotes-1.6.2.2.txt @@ -0,0 +1,45 @@ +GIT v1.6.2.2 Release Notes +========================== + +Fixes since v1.6.2.1 +-------------------- + +* A longstanding confusing description of what --pickaxe option of + git-diff does has been clarified in the documentation. + +* "git-blame -S" did not quite work near the commits that were given + on the command line correctly. + +* "git diff --pickaxe-regexp" did not count overlapping matches + correctly. + +* "git diff" did not feed files in work-tree representation to external + diff and textconv. + +* "git-fetch" in a repository that was not cloned from anywhere said + it cannot find 'origin', which was hard to understand for new people. + +* "git-format-patch --numbered-files --stdout" did not have to die of + incompatible options; it now simply ignores --numbered-files as no files + are produced anyway. + +* "git-ls-files --deleted" did not work well with GIT_DIR&GIT_WORK_TREE. + +* "git-read-tree A B C..." without -m option has been broken for a long + time. + +* git-send-email ignored --in-reply-to when --no-thread was given. + +* 'git-submodule add' did not tolerate extra slashes and ./ in the path it + accepted from the command line; it now is more lenient. + +* git-svn misbehaved when the project contained a path that began with + two dashes. + +* import-zips script (in contrib) did not compute the common directory + prefix correctly. + +* miscompilation of negated enum constants by old gcc (2.9) affected the + codepaths to spawn subprocesses. + +Many small documentation updates are included as well. diff --git a/Documentation/RelNotes-1.6.2.3.txt b/Documentation/RelNotes-1.6.2.3.txt new file mode 100644 index 0000000000..4d3c1ac91c --- /dev/null +++ b/Documentation/RelNotes-1.6.2.3.txt @@ -0,0 +1,22 @@ +GIT v1.6.2.3 Release Notes +========================== + +Fixes since v1.6.2.2 +-------------------- + +* Setting an octal mode value to core.sharedrepository configuration to + restrict access to the repository to group members did not work as + advertised. + +* A fairly large and trivial memory leak while rev-list shows list of + reachable objects has been identified and plugged. + +* "git-commit --interactive" did not abort when underlying "git-add -i" + signaled a failure. + +* git-repack (invoked from git-gc) did not work as nicely as it should in + a repository that borrows objects from neighbours via alternates + mechanism especially when some packs are marked with the ".keep" flag + to prevent them from being repacked. + +Many small documentation updates are included as well. diff --git a/Documentation/RelNotes-1.6.2.4.txt b/Documentation/RelNotes-1.6.2.4.txt new file mode 100644 index 0000000000..f4bf1d0986 --- /dev/null +++ b/Documentation/RelNotes-1.6.2.4.txt @@ -0,0 +1,39 @@ +GIT v1.6.2.4 Release Notes +========================== + +Fixes since v1.6.2.3 +-------------------- + +* The configuration parser had a buffer overflow while parsing an overlong + value. + +* pruning reflog entries that are unreachable from the tip of the ref + during "git reflog prune" (hence "git gc") was very inefficient. + +* "git-add -p" lacked a way to say "q"uit to refuse staging any hunks for + the remaining paths. You had to say "d" and then ^C. + +* "git-checkout <tree-ish> <submodule>" did not update the index entry at + the named path; it now does. + +* "git-fast-export" choked when seeing a tag that does not point at commit. + +* "git init" segfaulted when given an overlong template location via + the --template= option. + +* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when + deciding to descend into a subdirectory but they did not match the + individual paths correctly. This caused pathspecs "abc/d ab" to match + "abc/0" ("abc/d" made them decide to descend into the directory "abc/", + and then "ab" incorrectly matched "abc/0" when it shouldn't). + +* "git-merge-recursive" was broken when a submodule entry was involved in + a criss-cross merge situation. + +Many small documentation updates are included as well. + +--- +exec >/var/tmp/1 +echo O=$(git describe maint) +O=v1.6.2.3-38-g318b847 +git shortlog --no-merges $O..maint diff --git a/Documentation/RelNotes-1.6.2.5.txt b/Documentation/RelNotes-1.6.2.5.txt new file mode 100644 index 0000000000..b23f9e95d1 --- /dev/null +++ b/Documentation/RelNotes-1.6.2.5.txt @@ -0,0 +1,21 @@ +GIT v1.6.2.5 Release Notes +========================== + +Fixes since v1.6.2.4 +-------------------- + +* "git apply" mishandled if you fed a git generated patch that renames + file A to B and file B to A at the same time. + +* "git diff -c -p" (and "diff --cc") did not expect to see submodule + differences and instead refused to work. + +* "git grep -e '('" segfaulted, instead of diagnosing a mismatched + parentheses error. + +* "git fetch" generated packs with offset-delta encoding when both ends of + the connection are capable of producing one; this cannot be read by + ancient git and the user should be able to disable this by setting + repack.usedeltabaseoffset configuration to false. + + diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt new file mode 100644 index 0000000000..ad060f4f89 --- /dev/null +++ b/Documentation/RelNotes-1.6.2.txt @@ -0,0 +1,164 @@ +GIT v1.6.2 Release Notes +======================== + +With the next major release, "git push" into a branch that is +currently checked out will be refused by default. You can choose +what should happen upon such a push by setting the configuration +variable receive.denyCurrentBranch in the receiving repository. + +To ease the transition plan, the receiving repository of such a +push running this release will issue a big warning when the +configuration variable is missing. Please refer to: + + http://git.or.cz/gitwiki/GitFaq#non-bare + http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007 + +for more details on the reason why this change is needed and the +transition plan. + +For a similar reason, "git push $there :$killed" to delete the branch +$killed in a remote repository $there, if $killed branch is the current +branch pointed at by its HEAD, gets a large warning. You can choose what +should happen upon such a push by setting the configuration variable +receive.denyDeleteCurrent in the receiving repository. + + +Updates since v1.6.1 +-------------------- + +(subsystems) + +* git-svn updates. + +* gitweb updates, including a new patch view and RSS/Atom feed + improvements. + +* (contrib/emacs) git.el now has commands for checking out a branch, + creating a branch, cherry-picking and reverting commits; vc-git.el + is not shipped with git anymore (it is part of official Emacs). + +(performance) + +* pack-objects autodetects the number of CPUs available and uses threaded + version. + +(usability, bells and whistles) + +* 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 and acts as if you typed the branch name. + E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and + "git rev-parse --symbolic-full-name @{-1}" would work as expected. + +* When refs/remotes/origin/HEAD points at a remote tracking branch that + has been pruned away, many git operations issued warning when they + internally enumerated the refs. We now warn only when you say "origin" + to refer to that pruned branch. + +* The location of .mailmap file can be configured, and its file format was + enhanced to allow mapping an incorrect e-mail field as well. + +* "git add -p" learned 'g'oto action to jump directly to a hunk. + +* "git add -p" learned to find a hunk with given text with '/'. + +* "git add -p" optionally can be told to work with just the command letter + without Enter. + +* 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 am" learned --ignore-date option. + +* "git blame" aligns author names better when they are spelled in + non US-ASCII encoding. + +* "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 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" learned to handle 'noop' command some CVS clients seem + to expect to work. + +* "git diff" learned a new option --inter-hunk-context to coalesce close + hunks together and show context between them. + +* 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. + +* "git filter-branch" learned --prune-empty option that discards commits + that do not change the contents. + +* "git fsck" now checks loose objects in alternate object stores, instead + of misreporting them as missing. + +* "git gc --prune" was resurrected to allow "git gc --no-prune" and + giving non-default expiration period e.g. "git gc --prune=now". + +* "git grep -w" and "git grep" for fixed strings have been optimized. + +* "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 checkout" switches branches, taking the local changes while + switching to another commit. + +* "git submodule update" learned --no-fetch option. + +* "git tag" learned --contains that works the same way as the same option + from "git branch". + + +Fixes since v1.6.1 +------------------ + +All of the fixes in v1.6.1.X maintenance series are included in this +release, unless otherwise noted. + +Here are fixes that this release has, but have not been backported to +v1.6.1.X series. + +* "git-add sub/file" when sub is a submodule incorrectly added the path to + the superproject. + +* "git bundle" did not exclude annotated tags even when a range given + from the command line wanted to. + +* "git filter-branch" unnecessarily refused to work when you had + checked out a different commit from what is recorded in the superproject + index in a submodule. + +* "git filter-branch" incorrectly tried to update a nonexistent work tree + at the end when it is run in a bare repository. + +* "git gc" did not work if your repository was created with an ancient git + and never had any pack files in it before. + +* "git mergetool" used to ignore autocrlf and other attributes + based content rewriting. + +* branch switching and merges had a silly bug that did not validate + the correct directory when making sure an existing subdirectory is + clean. + +* "git -p cmd" when cmd is not a built-in one left the display in funny state + when killed in the middle. diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index ba07c8c571..9b559adefc 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -376,9 +376,36 @@ Thunderbird (A Large Angry SCM) +By default, Thunderbird will both wrap emails as well as flag them as +being 'format=flowed', both of which will make the resulting email unusable +by git. + Here are some hints on how to successfully submit patches inline using Thunderbird. +There are two different approaches. One approach is to configure +Thunderbird to not mangle patches. The second approach is to use +an external editor to keep Thunderbird from mangling the patches. + +Approach #1 (configuration): + +This recipe is current as of Thunderbird 2.0.0.19. Three steps: + 1. Configure your mail server composition as plain text + Edit...Account Settings...Composition & Addressing, + uncheck 'Compose Messages in HTML'. + 2. Configure your general composition window to not wrap + Edit..Preferences..Composition, wrap plain text messages at 0 + 3. Disable the use of format=flowed + Edit..Preferences..Advanced..Config Editor. Search for: + mailnews.send_plaintext_flowed + toggle it to make sure it is set to 'false'. + +After that is done, you should be able to compose email as you +otherwise would (cut + paste, git-format-patch | git-imap-send, etc), +and the patches should not be mangled. + +Approach #2 (external editor): + This recipe appears to work with the current [*1*] Thunderbird from Suse. The following Thunderbird extensions are needed: diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index 7f28432254..26cfb62195 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -39,7 +39,7 @@ of lines before or after the line given by <start>. Show raw timestamp (Default: off). -S <revs-file>:: - Use revs from revs-file instead of calling linkgit:git-rev-list[1]. + Use revisions from revs-file instead of calling linkgit:git-rev-list[1]. --reverse:: Walk history forward instead of backward. Instead of showing @@ -74,7 +74,7 @@ of lines before or after the line given by <start>. Detect moving lines in the file as well. When a commit moves a block of lines in a file (e.g. the original file has A and then B, and the commit changes it to B and - then A), traditional 'blame' algorithm typically blames + then A), the traditional 'blame' algorithm typically blames the lines that were moved up (i.e. B) to the parent and assigns blame to the lines that were moved down (i.e. A) to the child commit. With this option, both groups of lines @@ -90,8 +90,8 @@ commit. files that were modified in the same commit. This is useful when you reorganize your program and move code around across files. When this option is given twice, - the command looks for copies from all other files in the - parent for the commit that creates the file in addition. + the command additionally looks for copies from all other + files in the parent for the commit that creates the file. + <num> is optional but it is the lower bound on the number of alphanumeric characters that git must detect as moving diff --git a/Documentation/cat-texi.perl b/Documentation/cat-texi.perl index dbc133cd3c..828ec62554 100755 --- a/Documentation/cat-texi.perl +++ b/Documentation/cat-texi.perl @@ -18,8 +18,12 @@ close TMP; printf '\input texinfo @setfilename gitman.info -@documentencoding us-ascii -@node Top,,%s +@documentencoding UTF-8 +@dircategory Development +@direntry +* Git Man Pages: (gitman). Manual pages for Git revision control system +@end direntry +@node Top,,, (dir) @top Git Manual Pages @documentlanguage en @menu diff --git a/Documentation/config.txt b/Documentation/config.txt index 2ed868c81a..6af58ff3aa 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -290,8 +290,10 @@ core.sharedRepository:: group-shareable. When 'umask' (or 'false'), git will use permissions reported by umask(2). When '0xxx', where '0xxx' is an octal number, files in the repository will have this mode value. '0xxx' will override - user's umask value, and thus, users with a safe umask (0077) can use - this option. Examples: '0660' is equivalent to 'group'. '0640' is a + user's umask value (whereas the other options will only override + requested parts of the user's umask value). Examples: '0660' will make + the repo read/write-able for the owner and group, but inaccessible to + others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a repository that is group-readable but not group-writable. See linkgit:git-init[1]. False by default. @@ -556,8 +558,8 @@ color.interactive:: color.interactive.<slot>:: Use customized color for 'git-add --interactive' - output. `<slot>` may be `prompt`, `header`, or `help`, for - three distinct types of normal output from interactive + output. `<slot>` may be `prompt`, `header`, `help` or `error`, for + four distinct types of normal output from interactive programs. The values of these variables may be specified as in color.branch.<slot>. @@ -639,6 +641,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 @@ -725,6 +733,10 @@ gc.rerereunresolved:: kept for this many days when 'git-rerere gc' is run. The default is 15 days. See linkgit:git-rerere[1]. +gitcvs.commitmsgannotation:: + Append this string to each commit message. Set to empty string + to disable this feature. Defaults to "via git-CVS emulator". + gitcvs.enabled:: Whether the CVS server interface is enabled for this repository. See linkgit:git-cvsserver[1]. @@ -990,6 +1002,13 @@ instaweb.port:: The port number to bind the gitweb httpd to. See linkgit:git-instaweb[1]. +interactive.singlekey:: + In interactive programs, allow the user to provide one-letter + input with a single key (i.e., without hitting enter). + Currently this is used only by the `\--patch` mode of + linkgit:git-add[1]. Note that this setting is silently + ignored if portable keystroke input is not available. + log.date:: Set default date-time mode for the log command. Setting log.date value is similar to using 'git-log'\'s --date option. The value is one of the @@ -1002,6 +1021,14 @@ log.showroot:: Tools like linkgit:git-log[1] or linkgit:git-whatchanged[1], which normally hide the root commit will now show it. True by default. +mailmap.file:: + The location of an augmenting mailmap file. The default + mailmap, located in the root of the repository, is loaded + first, then the mailmap file pointed to by this variable. + The location of the mailmap file may be in a repository + subdirectory, or somewhere outside of the repository itself. + See linkgit:git-shortlog[1] and linkgit:git-blame[1]. + man.viewer:: Specify the programs that may be used to display help in the 'man' format. See linkgit:git-help[1]. @@ -1046,6 +1073,16 @@ mergetool.keepBackup:: is set to `false` then this file is not preserved. Defaults to `true` (i.e. keep the backup files). +mergetool.keepTemporaries:: + When invoking a custom merge tool, git uses a set of temporary + files to pass to the tool. If the tool returns an error and this + variable is set to `true`, then these temporary files will be + preserved, otherwise they will be removed after the tool has + exited. Defaults to `false`. + +mergetool.prompt:: + Prompt before each invocation of the merge resolution program. + pack.window:: The size of the window used by linkgit:git-pack-objects[1] when no window size is given on the command line. Defaults to 10. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index b432d2518a..9276faeb11 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 @@ -116,7 +133,7 @@ endif::git-format-patch[] --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object name in diff-raw format output and diff-tree header - lines, show only handful hexdigits prefix. This is + lines, show only a partial prefix. This is independent of --full-index option above, which controls the diff-patch output format. Non default number of digits can be specified with --abbrev=<n>. @@ -159,7 +176,10 @@ endif::git-format-patch[] number. -S<string>:: - Look for differences that contain the change in <string>. + Look for differences that introduce or remove an instance of + <string>. Note that this is different than the string simply + appearing in diff output; see the 'pickaxe' entry in + linkgit:gitdiffcore[7] for more details. --pickaxe-all:: When -S finds a change, show all the changes in that @@ -205,6 +225,10 @@ endif::git-format-patch[] differences even if one line has whitespace where the other line has none. +--inter-hunk-context=<lines>:: + Show the context between diff hunks, up to the specified number + of lines, thereby fusing hunks that are close to each other. + --exit-code:: Make the program exit with codes similar to diff(1). That is, it exits with 1 if there were differences and diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index e4c711bbd2..d938b42289 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -245,8 +245,11 @@ patch:: y - stage this hunk n - do not stage this hunk + q - quit, do not stage this hunk nor any of the remaining ones a - stage this and all the remaining hunks in the file d - do not stage this hunk nor any of the remaining hunks in the file + 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 @@ -263,13 +266,6 @@ diff:: This lets you review what will be committed (i.e. between HEAD and index). -Bugs ----- -The interactive mode does not work with files whose names contain -characters that need C-quoting. `core.quotepath` configuration can be -used to work this limitation around to some degree, but backslash, -double-quote and control characters will still have problems. - SEE ALSO -------- linkgit:git-status[1] diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index b9c6fac748..6d92cbee64 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -10,8 +10,10 @@ SYNOPSIS -------- [verse] 'git am' [--signoff] [--keep] [--utf8 | --no-utf8] - [--3way] [--interactive] - [--whitespace=<option>] [-C<n>] [-p<n>] + [--3way] [--interactive] [--committer-date-is-author-date] + [--ignore-date] + [--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>] + [--reject] [<mbox> | <Maildir>...] 'git am' (--skip | --resolved | --abort) @@ -25,12 +27,12 @@ OPTIONS ------- <mbox>|<Maildir>...:: The list of mailbox files to read patches from. If you do not - supply this argument, reads from the standard input. If you supply - directories, they'll be treated as Maildirs. + supply this argument, the command reads from the standard input. + If you supply directories, they will be treated as Maildirs. -s:: --signoff:: - Add `Signed-off-by:` line to the commit message, using + Add a `Signed-off-by:` line to the commit message, using the committer identity of yourself. -k:: @@ -46,7 +48,7 @@ OPTIONS preferred encoding if it is not UTF-8). + This was optional in prior versions of git, but now it is the -default. You could use `--no-utf8` to override this. +default. You can use `--no-utf8` to override this. --no-utf8:: Pass `-n` flag to 'git-mailinfo' (see @@ -55,17 +57,15 @@ default. You could use `--no-utf8` to override this. -3:: --3way:: When the patch does not apply cleanly, fall back on - 3-way merge, if the patch records the identity of blobs - it is supposed to apply to, and we have those blobs + 3-way merge if the patch records the identity of blobs + it is supposed to apply to and we have those blobs available locally. --whitespace=<option>:: - This flag is passed to the 'git-apply' (see linkgit:git-apply[1]) - program that applies - the patch. - -C<n>:: -p<n>:: +--directory=<dir>:: +--reject:: These flags are passed to the 'git-apply' (see linkgit:git-apply[1]) program that applies the patch. @@ -74,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 + value 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 the author date by using the same + value as the committer date. + --skip:: Skip the current patch. This is only meaningful when restarting an aborted patch. @@ -101,24 +115,24 @@ DISCUSSION ---------- The commit author name is taken from the "From: " line of the -message, and commit author time is taken from the "Date: " line +message, and commit author date is taken from the "Date: " line of the message. The "Subject: " line is used as the title of the commit, after stripping common prefix "[PATCH <anything>]". -It is supposed to describe what the commit is about concisely as -a one line text. +The "Subject: " line is supposed to concisely describe what the +commit is about in one line of text. -The body of the message (iow, after a blank line that terminates -RFC2822 headers) can begin with "Subject: " and "From: " lines -that are different from those of the mail header, to override -the values of these fields. +"From: " and "Subject: " lines starting the body (the rest of the +message after the blank line terminating the RFC2822 headers) +override the respective commit author name and title values taken +from the headers. The commit message is formed by the title taken from the "Subject: ", a blank line and the body of the message up to -where the patch begins. Excess whitespaces at the end of the -lines are automatically stripped. +where the patch begins. Excess whitespace at the end of each +line is automatically stripped. The patch is expected to be inline, directly following the -message. Any line that is of form: +message. Any line that is of the form: * three-dashes and end-of-line, or * a line that begins with "diff -", or @@ -127,18 +141,18 @@ message. Any line that is of form: is taken as the beginning of a patch, and the commit log message is terminated before the first occurrence of such a line. -When initially invoking it, you give it names of the mailboxes -to crunch. Upon seeing the first patch that does not apply, it -aborts in the middle,. You can recover from this in one of two ways: +When initially invoking `git am`, you give it the names of the mailboxes +to process. Upon seeing the first patch that does not apply, it +aborts in the middle. You can recover from this in one of two ways: -. skip the current patch by re-running the command with '--skip' +. skip the current patch by re-running the command with the '--skip' option. . hand resolve the conflict in the working directory, and update - the index file to bring it in a state that the patch should - have produced. Then run the command with '--resolved' option. + the index file to bring it into a state that the patch should + have produced. Then run the command with the '--resolved' option. -The command refuses to process new mailboxes while `.git/rebase-apply` +The command refuses to process new mailboxes while the `.git/rebase-apply` directory exists, so if you decide to start over from scratch, run `rm -f -r .git/rebase-apply` before running the command with mailbox names. diff --git a/Documentation/git-annotate.txt b/Documentation/git-annotate.txt index 0aba022ba6..0590eec056 100644 --- a/Documentation/git-annotate.txt +++ b/Documentation/git-annotate.txt @@ -3,7 +3,7 @@ git-annotate(1) NAME ---- -git-annotate - Annotate file lines with commit info +git-annotate - Annotate file lines with commit information SYNOPSIS -------- @@ -12,11 +12,11 @@ SYNOPSIS DESCRIPTION ----------- Annotates each line in the given file with information from the commit -which introduced the line. Optionally annotate from a given revision. +which introduced the line. Optionally annotates from a given revision. The only difference between this command and linkgit:git-blame[1] is that they use slightly different output formats, and this command exists only -for backward compatibility to support existing scripts, and provide more +for backward compatibility to support existing scripts, and provide a more familiar command name for people coming from other SCM systems. OPTIONS diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 32f2b85a10..9e5baa2777 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git apply' [--stat] [--numstat] [--summary] [--check] [--index] - [--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse] + [--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse] [--allow-binary-replacement | --binary] [--reject] [-z] [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached] [--whitespace=<nowarn|warn|fix|error|error-all>] @@ -25,7 +25,7 @@ and a work tree. OPTIONS ------- <patch>...:: - The files to read patch from. '-' can be used to read + The files to read the patch from. '-' can be used to read from the standard input. --stat:: @@ -33,8 +33,8 @@ OPTIONS input. Turns off "apply". --numstat:: - Similar to \--stat, but shows number of added and - deleted lines in decimal notation and pathname without + Similar to \--stat, but shows the number of added and + deleted lines in decimal notation and the pathname without abbreviation, to make it more machine friendly. For binary files, outputs two `-` instead of saying `0 0`. Turns off "apply". @@ -60,15 +60,15 @@ OPTIONS causes the index file to be updated. --cached:: - Apply a patch without touching the working tree. Instead, take the - cached data, apply the patch, and store the result in the index, + Apply a patch without touching the working tree. Instead take the + cached data, apply the patch, and store the result in the index without using the working tree. This implies '--index'. ---build-fake-ancestor <file>:: +--build-fake-ancestor=<file>:: Newer 'git-diff' output has embedded 'index information' for each blob to help identify the original version that the patch applies to. When this flag is given, and if - the original versions of the blobs is available locally, + the original versions of the blobs are available locally, builds a temporary index containing those blobs. + When a pure mode change is encountered (which has no index information), @@ -109,13 +109,13 @@ the information is read from the current index instead. applying a diff generated with --unified=0. To bypass these checks use '--unidiff-zero'. + -Note, for the reasons stated above usage of context-free patches are +Note, for the reasons stated above usage of context-free patches is discouraged. --apply:: If you use any of the options marked "Turns off 'apply'" above, 'git-apply' reads and outputs the - information you asked without actually applying the + requested information without actually applying the patch. Give this flag after those flags to also apply the patch. @@ -124,7 +124,7 @@ discouraged. patch. This can be used to extract the common part between two files by first running 'diff' on them and applying the result with this option, which would apply the - deletion part but not addition part. + deletion part but not the addition part. --allow-binary-replacement:: --binary:: @@ -162,7 +162,7 @@ By default, the command outputs warning messages but applies the patch. When `git-apply` is used for statistics and not applying a patch, it defaults to `nowarn`. + -You can use different `<action>` to control this +You can use different `<action>` values to control this behavior: + * `nowarn` turns off the trailing whitespace warning. @@ -170,7 +170,7 @@ behavior: patch as-is (default). * `fix` outputs warnings for a few such errors, and applies the patch after fixing them (`strip` is a synonym --- the tool - used to consider only trailing whitespaces as errors, and the + used to consider only trailing whitespace characters as errors, and the fix involved 'stripping' them, but modern gits do more). * `error` outputs warnings for a few such errors, and refuses to apply the patch. @@ -195,7 +195,7 @@ behavior: adjusting the hunk headers appropriately). --directory=<root>:: - Prepend <root> to all filenames. If a "-p" argument was passed, too, + Prepend <root> to all filenames. If a "-p" argument was also passed, it is applied before prepending the new root. + For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh` @@ -221,7 +221,7 @@ ignored, i.e., they are not required to be up-to-date or clean and they are not updated. If --index is not specified, then the submodule commits in the patch -are ignored and only the absence of presence of the corresponding +are ignored and only the absence or presence of the corresponding subdirectory is checked and (if possible) updated. Author diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index 41cbf9c081..ad38f7f39d 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -22,7 +22,7 @@ prepended to the filenames in the archive. 'git-archive' behaves differently when given a tree ID versus when given a commit ID or tag ID. In the first case the current time is -used as modification time of each file in the archive. In the latter +used as the modification time of each file in the archive. In the latter case the commit time as recorded in the referenced commit object is used instead. Additionally the commit ID is stored in a global extended pax header if the tar format is used; it can be extracted @@ -48,11 +48,11 @@ OPTIONS Prepend <prefix>/ to each filename in the archive. <extra>:: - This can be any options that the archiver backend understand. + This can be any options that the archiver backend understands. See next section. --remote=<repo>:: - Instead of making a tar archive from local repository, + Instead of making a tar archive from the local repository, retrieve a tar archive from a remote repository. --exec=<git-upload-archive>:: @@ -88,12 +88,24 @@ tar.umask:: archiving user's umask will be used instead. See umask(2) for details. +ATTRIBUTES +---------- + +export-ignore:: + Files and directories with the attribute export-ignore won't be + added to archive files. See linkgit:gitattributes[5] for details. + +export-subst:: + If the attribute export-subst is set for a file then git will + expand several placeholders when adding this file to an archive. + See linkgit:gitattributes[5] for details. + EXAMPLES -------- git archive --format=tar --prefix=junk/ HEAD | (cd /var/tmp/ && tar xf -):: Create a tar archive that contains the contents of the - latest commit on the current branch, and extracts it in + latest commit on the current branch, and extract it in the `/var/tmp/junk` directory. git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz:: @@ -110,6 +122,11 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs Put everything in the current head's Documentation/ directory into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'. + +SEE ALSO +-------- +linkgit:gitattributes[5] + Author ------ Written by Franck Bui-Huu and Rene Scharfe. diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index 147ea38197..e5862b9dbb 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -3,7 +3,7 @@ git-bisect(1) NAME ---- -git-bisect - Find the change that introduced a bug by binary search +git-bisect - Find by binary search the change that introduced a bug SYNOPSIS @@ -39,7 +39,8 @@ help" or "git bisect -h" to get a long usage description. Basic bisect commands: start, bad, good ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The way you use it is: +Using the Linux kernel tree as an example, basic use of the bisect +command is as follows: ------------------------------------------------ $ git bisect start @@ -48,61 +49,63 @@ $ git bisect good v2.6.13-rc2 # v2.6.13-rc2 was the last version # tested that was good ------------------------------------------------ -When you give at least one bad and one good versions, it will bisect -the revision tree and say something like: +When you have specified at least one bad and one good version, the +command bisects the revision tree and outputs something similar to +the following: ------------------------------------------------ Bisecting: 675 revisions left to test after this ------------------------------------------------ -and check out the state in the middle. Now, compile that kernel, and -boot it. Now, let's say that this booted kernel works fine, then just -do +The state in the middle of the set of revisions is then checked out. +You would now compile that kernel and boot it. If the booted kernel +works correctly, you would then issue the following command: ------------------------------------------------ $ git bisect good # this one is good ------------------------------------------------ -which will now say +The output of this command would be something similar to the following: ------------------------------------------------ Bisecting: 337 revisions left to test after this ------------------------------------------------ -and you continue along, compiling that one, testing it, and depending -on whether it is good or bad, you say "git bisect good" or "git bisect -bad", and ask for the next bisection. +You keep repeating this process, compiling the tree, testing it, and +depending on whether it is good or bad issuing the command "git bisect good" +or "git bisect bad" to ask for the next bisection. -Until you have no more left, and you'll have been left with the first -bad kernel rev in "refs/bisect/bad". +Eventually there will be no more revisions left to bisect, and you +will have been left with the first bad kernel revision in "refs/bisect/bad". Bisect reset ~~~~~~~~~~~~ -Oh, and then after you want to reset to the original head, do a +To return to the original head after a bisect session, issue the +following command: ------------------------------------------------ $ git bisect reset ------------------------------------------------ -to get back to the original branch, instead of being on the bisection -commit ("git bisect start" will do that for you too, actually: it will -reset the bisection state). +This resets the tree to the original branch instead of being on the +bisection commit ("git bisect start" will also do that, as it resets +the bisection state). Bisect visualize ~~~~~~~~~~~~~~~~ -During the bisection process, you can say +To see the currently remaining suspects in 'gitk', issue the following +command during the bisection process: ------------ $ git bisect visualize ------------ -to see the currently remaining suspects in 'gitk'. `visualize` is a bit -too long to type and `view` is provided as a synonym. +`view` may also be used as a synonym for `visualize`. -If 'DISPLAY' environment variable is not set, 'git log' is used -instead. You can even give command line options such as `-p` and +If the 'DISPLAY' environment variable is not set, 'git log' is used +instead. You can also give command line options such as `-p` and `--stat`. ------------ @@ -112,57 +115,58 @@ $ git bisect view --stat Bisect log and bisect replay ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The good/bad input is logged, and +After having marked revisions as good or bad, issue the following +command to show what has been done so far: ------------ $ git bisect log ------------ -shows what you have done so far. You can truncate its output somewhere -and save it in a file, and run +If you discover that you made a mistake in specifying the status of a +revision, you can save the output of this command to a file, edit it to +remove the incorrect entries, and then issue the following commands to +return to a corrected state: ------------ +$ git bisect reset $ git bisect replay that-file ------------ -if you find later you made a mistake telling good/bad about a -revision. - -Avoiding to test a commit +Avoiding testing a commit ~~~~~~~~~~~~~~~~~~~~~~~~~ -If in a middle of bisect session, you know what the bisect suggested -to try next is not a good one to test (e.g. the change the commit +If, in the middle of a bisect session, you know that the next suggested +revision is not a good one to test (e.g. the change the commit introduces is known not to work in your environment and you know it does not have anything to do with the bug you are chasing), you may -want to find a near-by commit and try that instead. +want to find a nearby commit and try that instead. -It goes something like this: +For example: ------------ -$ git bisect good/bad # previous round was good/bad. +$ git bisect good/bad # previous round was good or bad. Bisecting: 337 revisions left to test after this $ git bisect visualize # oops, that is uninteresting. -$ git reset --hard HEAD~3 # try 3 revs before what +$ git reset --hard HEAD~3 # try 3 revisions before what # was suggested ------------ -Then compile and test the one you chose to try. After that, tell -bisect what the result was as usual. +Then compile and test the chosen revision, and afterwards mark +the revision as good or bad in the usual manner. Bisect skip ~~~~~~~~~~~~ -Instead of choosing by yourself a nearby commit, you may just want git -to do it for you using: +Instead of choosing by yourself a nearby commit, you can ask git +to do it for you by issuing the command: ------------ $ git bisect skip # Current version cannot be tested ------------ But computing the commit to test may be slower afterwards and git may -eventually not be able to tell the first bad among a bad and one or -more "skip"ped commits. +eventually not be able to tell the first bad commit among a bad commit +and one or more skipped commits. You can even skip a range of commits, instead of just one commit, using the "'<commit1>'..'<commit2>'" notation. For example: @@ -171,33 +175,34 @@ using the "'<commit1>'..'<commit2>'" notation. For example: $ git bisect skip v2.5..v2.6 ------------ -would mean that no commit between `v2.5` excluded and `v2.6` included -can be tested. +This tells the bisect process that no commit after `v2.5`, up to and +including `v2.6`, should be tested. -Note that if you want to also skip the first commit of a range you can -use something like: +Note that if you also want to skip the first commit of the range you +would issue the command: ------------ $ git bisect skip v2.5 v2.5..v2.6 ------------ -and the commit pointed to by `v2.5` will be skipped too. +This tells the bisect process that the commits between `v2.5` included +and `v2.6` included should be skipped. + Cutting down bisection by giving more parameters to bisect start ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can further cut down the number of trials if you know what part of -the tree is involved in the problem you are tracking down, by giving -paths parameters when you say `bisect start`, like this: +You can further cut down the number of trials, if you know what part of +the tree is involved in the problem you are tracking down, by specifying +path parameters when issuing the `bisect start` command: ------------ $ git bisect start -- arch/i386 include/asm-i386 ------------ -If you know beforehand more than one good commits, you can narrow the -bisect space down without doing the whole tree checkout every time you -give good commits. You give the bad revision immediately after `start` -and then you give all the good revisions you have: +If you know beforehand more than one good commit, you can narrow the +bisect space down by specifying all of the good commits immediately after +the bad commit when issuing the `bisect start` command: ------------ $ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 -- @@ -209,38 +214,38 @@ Bisect run ~~~~~~~~~~ If you have a script that can tell if the current source code is good -or bad, you can automatically bisect using: +or bad, you can bisect by issuing the command: ------------ $ git bisect run my_script ------------ -Note that the "run" script (`my_script` in the above example) should -exit with code 0 in case the current source code is good. Exit with a +Note that the script (`my_script` in the above example) should +exit with code 0 if the current source code is good, and exit with a code between 1 and 127 (inclusive), except 125, if the current source code is bad. -Any other exit code will abort the automatic bisect process. (A -program that does "exit(-1)" leaves $? = 255, see exit(3) manual page, -the value is chopped with "& 0377".) +Any other exit code will abort the bisect process. It should be noted +that a program that terminates via "exit(-1)" leaves $? = 255, (see the +exit(3) manual page), as the value is chopped with "& 0377". The special exit code 125 should be used when the current source code -cannot be tested. If the "run" script exits with this code, the current -revision will be skipped, see `git bisect skip` above. +cannot be tested. If the script exits with this code, the current +revision will be skipped (see `git bisect skip` above). -You may often find that during bisect you want to have near-constant -tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or -"revision that does not have this commit needs this patch applied to -work around other problem this bisection is not interested in") -applied to the revision being tested. +You may often find that during a bisect session you want to have +temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a +header file, or "revision that does not have this commit needs this +patch applied to work around another problem this bisection is not +interested in") applied to the revision being tested. To cope with such a situation, after the inner 'git bisect' finds the -next revision to test, with the "run" script, you can apply that tweak -before compiling, run the real test, and after the test decides if the -revision (possibly with the needed tweaks) passed the test, rewind the -tree to the pristine state. Finally the "run" script can exit with -the status of the real test to let the "git bisect run" command loop to -determine the outcome. +next revision to test, the script can apply the patch +before compiling, run the real test, and afterwards decide if the +revision (possibly with the needed patch) passed the test and then +rewind the tree to the pristine state. Finally the script should exit +with the status of the real test to let the "git bisect run" command loop +determine the eventual outcome of the bisect session. EXAMPLES -------- @@ -257,39 +262,39 @@ $ git bisect run make # "make" builds the app ------------ $ cat ~/test.sh #!/bin/sh -make || exit 125 # this "skip"s broken builds +make || exit 125 # this skips broken builds make test # "make test" runs the test suite $ git bisect start v1.3 v1.1 -- # v1.3 is bad, v1.1 is good $ git bisect run ~/test.sh ------------ + Here we use a "test.sh" custom script. In this script, if "make" -fails, we "skip" the current commit. +fails, we skip the current commit. + -It's safer to use a custom script outside the repo to prevent +It is safer to use a custom script outside the repository to prevent interactions between the bisect, make and test processes and the script. + -And "make test" should "exit 0", if the test suite passes, and -"exit 1" (for example) otherwise. +"make test" should "exit 0", if the test suite passes, and +"exit 1" otherwise. * Automatically bisect a broken test case: + ------------ $ cat ~/test.sh #!/bin/sh -make || exit 125 # this "skip"s broken builds +make || exit 125 # this skips broken builds ~/check_test_case.sh # does the test case passes ? $ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 $ git bisect run ~/test.sh ------------ + -Here "check_test_case.sh" should "exit 0", if the test case passes, -and "exit 1" (for example) otherwise. +Here "check_test_case.sh" should "exit 0" if the test case passes, +and "exit 1" otherwise. + -It's safer if both "test.sh" and "check_test_case.sh" scripts are -outside the repo to prevent interactions between the bisect, make and -test processes and the scripts. +It is safer if both "test.sh" and "check_test_case.sh" scripts are +outside the repository to prevent interactions between the bisect, +make and test processes and the scripts. Author ------ diff --git a/Documentation/git-blame.txt b/Documentation/git-blame.txt index cc934e55c3..8c7b7b0838 100644 --- a/Documentation/git-blame.txt +++ b/Documentation/git-blame.txt @@ -18,9 +18,9 @@ DESCRIPTION Annotates each line in the given file with information from the revision which last modified the line. Optionally, start annotating from the given revision. -Also it can limit the range of lines annotated. +The command can also limit the range of lines annotated. -This report doesn't tell you anything about lines which have been deleted or +The report does not tell you anything about lines which have been deleted or replaced; you need to use a tool such as 'git-diff' or the "pickaxe" interface briefly mentioned in the following paragraph. @@ -48,26 +48,26 @@ include::blame-options.txt[] lines between files (see `-C`) and lines moved within a file (see `-M`). The first number listed is the score. This is the number of alphanumeric characters detected - to be moved between or within files. This must be above + as having been moved between or within files. This must be above a certain threshold for 'git-blame' to consider those lines of code to have been moved. -f:: --show-name:: - Show filename in the original commit. By default - filename is shown if there is any line that came from a - file with different name, due to rename detection. + Show the filename in the original commit. By default + the filename is shown if there is any line that came from a + file with a different name, due to rename detection. -n:: --show-number:: - Show line number in the original commit (Default: off). + Show the line number in the original commit (Default: off). -s:: - Suppress author name and timestamp from the output. + Suppress the author name and timestamp from the output. -w:: - Ignore whitespace when comparing parent's version and - child's to find where the lines came from. + Ignore whitespace when comparing the parent's version and + the child's to find where the lines came from. THE PORCELAIN FORMAT @@ -79,17 +79,17 @@ header at the minimum has the first line which has: - 40-byte SHA-1 of the commit the line is attributed to; - the line number of the line in the original file; - the line number of the line in the final file; -- on a line that starts a group of line from a different +- on a line that starts a group of lines from a different commit than the previous one, the number of lines in this group. On subsequent lines this field is absent. This header line is followed by the following information at least once for each commit: -- author name ("author"), email ("author-mail"), time +- the author name ("author"), email ("author-mail"), time ("author-time"), and timezone ("author-tz"); similarly for committer. -- filename in the commit the line is attributed to. +- the filename in the commit that the line is attributed to. - the first line of the commit log message ("summary"). The contents of the actual line is output after the above @@ -100,23 +100,23 @@ header elements later. SPECIFYING RANGES ----------------- -Unlike 'git-blame' and 'git-annotate' in older git, the extent -of annotation can be limited to both line ranges and revision +Unlike 'git-blame' and 'git-annotate' in older versions of git, the extent +of the annotation can be limited to both line ranges and revision ranges. When you are interested in finding the origin for -ll. 40-60 for file `foo`, you can use `-L` option like these +lines 40-60 for file `foo`, you can use the `-L` option like so (they mean the same thing -- both ask for 21 lines starting at line 40): git blame -L 40,60 foo git blame -L 40,+21 foo -Also you can use regular expression to specify the line range. +Also you can use a regular expression to specify the line range: git blame -L '/^sub hello {/,/^}$/' foo -would limit the annotation to the body of `hello` subroutine. +which limits the annotation to the body of the `hello` subroutine. -When you are not interested in changes older than the version +When you are not interested in changes older than version v2.6.18, or changes older than 3 weeks, you can use revision range specifiers similar to 'git-rev-list': @@ -129,7 +129,7 @@ commit v2.6.18 or the most recent commit that is more than 3 weeks old in the above example) are blamed for that range boundary commit. -A particularly useful way is to see if an added file have lines +A particularly useful way is to see if an added file has lines created by copy-and-paste from existing files. Sometimes this indicates that the developer was being sloppy and did not refactor the code properly. You can first find the commit that @@ -162,26 +162,32 @@ annotated. + Line numbers count from 1. -. The first time that commit shows up in the stream, it has various +. The first time that a commit shows up in the stream, it has various other information about it printed out with a one-word tag at the - beginning of each line about that "extended commit info" (author, - email, committer, dates, summary etc). + beginning of each line describing the extra commit information (author, + email, committer, dates, summary, etc.). -. Unlike Porcelain format, the filename information is always +. Unlike the Porcelain format, the filename information is always given and terminates the entry: "filename" <whitespace-quoted-filename-goes-here> + -and thus it's really quite easy to parse for some line- and word-oriented +and thus it is really quite easy to parse for some line- and word-oriented parser (which should be quite natural for most scripting languages). + [NOTE] For people who do parsing: to make it more robust, just ignore any -lines in between the first and last one ("<sha1>" and "filename" lines) -where you don't recognize the tag-words (or care about that particular +lines between the first and last one ("<sha1>" and "filename" lines) +where you do not recognize the tag words (or care about that particular one) at the beginning of the "extended information" lines. That way, if there is ever added information (like the commit encoding or extended -commit commentary), a blame viewer won't ever care. +commit commentary), a blame viewer will not care. + + +MAPPING AUTHORS +--------------- + +include::mailmap.txt[] SEE ALSO diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 6103d62fe3..7f7b781f24 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -18,19 +18,19 @@ SYNOPSIS DESCRIPTION ----------- -With no arguments, existing branches are listed, the current branch will +With no arguments, existing branches are listed and the current branch will be highlighted with an asterisk. Option `-r` causes the remote-tracking branches to be listed, and option `-a` shows both. -With `--contains`, shows only the branches that contains the named commit -(in other words, the branches whose tip commits are descendant of the +With `--contains`, shows only the branches that contain the named commit +(in other words, the branches whose tip commits are descendants of the named commit). With `--merged`, only branches merged into the named commit (i.e. the branches whose tip commits are reachable from the named commit) will be listed. With `--no-merged` only branches not merged into -the named commit will be listed. Missing <commit> argument defaults to -'HEAD' (i.e. the tip of the current branch). +the named commit will be listed. If the <commit> argument is missing it +defaults to 'HEAD' (i.e. the tip of the current branch). -In its second form, a new branch named <branchname> will be created. +In the command's second form, a new branch named <branchname> will be created. It will start out with a head equal to the one given as <start-point>. If no <start-point> is given, the branch will be created with a head equal to that of the currently checked out branch. @@ -57,9 +57,9 @@ has a reflog then the reflog will also be deleted. Use -r together with -d to delete remote-tracking branches. Note, that it only makes sense to delete remote-tracking branches if they no longer exist -in remote repository or if 'git-fetch' was configured not to fetch -them again. See also 'prune' subcommand of linkgit:git-remote[1] for way to -clean up all obsolete remote-tracking branches. +in the remote repository or if 'git-fetch' was configured not to fetch +them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a +way to clean up all obsolete remote-tracking branches. OPTIONS @@ -83,7 +83,7 @@ OPTIONS Move/rename a branch and the corresponding reflog. -M:: - Move/rename a branch even if the new branchname already exists. + Move/rename a branch even if the new branch name already exists. --color:: Color branches to highlight current, local, and remote branches. @@ -103,17 +103,17 @@ OPTIONS Show sha1 and commit subject line for each head. --abbrev=<length>:: - Alter minimum display length for sha1 in output listing, - default value is 7. + Alter the sha1's minimum display length in the output listing. + The default value is 7. --no-abbrev:: - Display the full sha1s in output listing rather than abbreviating them. + Display the full sha1s in the output listing rather than abbreviating them. --track:: - When creating a new branch, set up configuration so that 'git-pull' + When creating a new branch, set up the configuration so that 'git-pull' will automatically retrieve data from the start point, which must be a branch. Use this if you always pull from the same upstream branch - into the new branch, and if you don't want to use "git pull + into the new branch, and if you do not want to use "git pull <repository> <refspec>" explicitly. This behavior is the default when the start point is a remote branch. Set the branch.autosetupmerge configuration variable to `false` if you want @@ -149,13 +149,13 @@ OPTIONS <newbranch>:: The new name for an existing branch. The same restrictions as for - <branchname> applies. + <branchname> apply. Examples -------- -Start development off of a known tag:: +Start development from a known tag:: + ------------ $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6 @@ -167,7 +167,7 @@ $ git checkout my2.6.14 <1> This step and the next one could be combined into a single step with "checkout -b my2.6.14 v2.6.14". -Delete unneeded branch:: +Delete an unneeded branch:: + ------------ $ git clone git://git.kernel.org/.../git.git my.git @@ -176,21 +176,21 @@ $ git branch -d -r origin/todo origin/html origin/man <1> $ git branch -D test <2> ------------ + -<1> Delete remote-tracking branches "todo", "html", "man". Next 'fetch' or -'pull' will create them again unless you configure them not to. See -linkgit:git-fetch[1]. -<2> Delete "test" branch even if the "master" branch (or whichever branch is -currently checked out) does not have all commits from test branch. +<1> Delete the remote-tracking branches "todo", "html" and "man". The next +'fetch' or 'pull' will create them again unless you configure them not to. +See linkgit:git-fetch[1]. +<2> Delete the "test" branch even if the "master" branch (or whichever branch +is currently checked out) does not have all commits from the test branch. Notes ----- -If you are creating a branch that you want to immediately checkout, it's +If you are creating a branch that you want to checkout immediately, it is easier to use the git checkout command with its `-b` option to create a branch and check it out with a single command. -The options `--contains`, `--merged` and `--no-merged` serves three related +The options `--contains`, `--merged` and `--no-merged` serve three related but different purposes: - `--contains <commit>` is used to find all branches which will need diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt index 1b66ab743c..aee7e4a8c9 100644 --- a/Documentation/git-bundle.txt +++ b/Documentation/git-bundle.txt @@ -19,13 +19,13 @@ DESCRIPTION Some workflows require that one or more branches of development on one machine be replicated on another machine, but the two machines cannot -be directly connected so the interactive git protocols (git, ssh, -rsync, http) cannot be used. This command provides support for +be directly connected, and therefore the interactive git protocols (git, +ssh, rsync, http) cannot be used. This command provides support for 'git-fetch' and 'git-pull' to operate by packaging objects and references in an archive at the originating machine, then importing those into another repository using 'git-fetch' and 'git-pull' after moving the archive by some means (i.e., by sneakernet). As no -direct connection between repositories exists, the user must specify a +direct connection between the repositories exists, the user must specify a basis for the bundle that is held by the destination repository: the bundle assumes that all objects in the basis are already in the destination repository. @@ -43,7 +43,7 @@ verify <file>:: bundle format itself as well as checking that the prerequisite commits exist and are fully linked in the current repository. 'git-bundle' prints a list of missing commits, if any, and exits - with non-zero status. + with a non-zero status. list-heads <file>:: Lists the references defined in the bundle. If followed by a @@ -53,14 +53,14 @@ list-heads <file>:: unbundle <file>:: Passes the objects in the bundle to 'git-index-pack' for storage in the repository, then prints the names of all - defined references. If a reflist is given, only references - matching those in the given list are printed. This command is + defined references. If a list of references is given, only + references matching those in the list are printed. This command is really plumbing, intended to be called only by 'git-fetch'. [git-rev-list-args...]:: A list of arguments, acceptable to 'git-rev-parse' and - 'git-rev-list', that specify the specific objects and references - to transport. For example, "master~10..master" causes the + 'git-rev-list', that specifies the specific objects and references + to transport. For example, `master\~10..master` causes the current master reference to be packaged along with all objects added since its 10th ancestor commit. There is no explicit limit to the number of references and objects that may be @@ -71,98 +71,134 @@ unbundle <file>:: A list of references used to limit the references reported as available. This is principally of use to 'git-fetch', which expects to receive only those references asked for and not - necessarily everything in the pack (in this case, 'git-bundle' is - acting like 'git-fetch-pack'). + necessarily everything in the pack (in this case, 'git-bundle' acts + like 'git-fetch-pack'). SPECIFYING REFERENCES --------------------- 'git-bundle' will only package references that are shown by 'git-show-ref': this includes heads, tags, and remote heads. References -such as master~1 cannot be packaged, but are perfectly suitable for +such as `master\~1` cannot be packaged, but are perfectly suitable for 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). +specified explicitly (e.g. `^master\~10`), or implicitly (e.g. +`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 -to contain objects already in the destination as these are ignored +It is okay to err on the side of caution, causing the bundle file +to contain objects already in the destination, as these are ignored 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. +but we can move data from A to B via some mechanism (CD, email, etc.). +We want to update R2 with development made on the 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 does not have +any basis. You can use a tag to remember up to what commit you last +processed, in order to make it easy to later update the other repository +with an incremental bundle: -- Without basis. -+ -This is useful when sending the whole history. +---------------- +machineA$ cd R1 +machineA$ git bundle create file.bundle master +machineA$ git tag -f lastR2bundle master +---------------- ------------- -$ git bundle create mybundle master ------------- +Then you transfer file.bundle to the target machine B. If you are creating +the repository on machine B, then you can clone from the bundle as if it +were a remote repository instead of creating an empty repository and then +pulling or fetching objects from the bundle: -- 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.bundle 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. The $GIT_DIR/config file in R2 will +have an entry like this: -- Using a tag present in both repositories +------------------------ +[remote "origin"] + url = /home/me/tmp/file.bundle + fetch = refs/heads/*:refs/remotes/origin/* +------------------------ + +To update the resulting mine.git repository, you can fetch or pull after +replacing the bundle stored at /home/me/tmp/file.bundle with incremental +updates. + +After working some more in the original repository, you can create an +incremental bundle to update the other repository: + +---------------- +machineA$ cd R1 +machineA$ git bundle create file.bundle lastR2bundle..master +machineA$ git tag -f lastR2bundle master +---------------- + +You then transfer the bundle to the other machine to replace +/home/me/tmp/file.bundle, 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, 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 any other options that 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: +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 do not 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 which it fetches or pulls from. You can, for example, map +references 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 ------------- +You can also see what references 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-cat-file.txt b/Documentation/git-cat-file.txt index 668f697c2a..b191276d7a 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -3,7 +3,7 @@ git-cat-file(1) NAME ---- -git-cat-file - Provide content or type/size information for repository objects +git-cat-file - Provide content or type and size information for repository objects SYNOPSIS @@ -14,19 +14,19 @@ SYNOPSIS DESCRIPTION ----------- -In the first form, provides content or type of objects in the repository. The -type is required unless '-t' or '-p' is used to find the object type, or '-s' -is used to find the object size. +In its first form, the command provides the content or the type of an object in +the repository. The type is required unless '-t' or '-p' is used to find the +object type, or '-s' is used to find the object size. -In the second form, a list of object (separated by LFs) is provided on stdin, -and the SHA1, type, and size of each object is printed on stdout. +In the second form, a list of objects (separated by linefeeds) is provided on +stdin, and the SHA1, type, and size of each object is printed on stdout. OPTIONS ------- <object>:: The name of the object to show. For a more complete list of ways to spell object names, see - "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. + the "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1]. -t:: Instead of the content, show the object type identified by @@ -56,8 +56,8 @@ OPTIONS stdin. May not be combined with any other options or arguments. --batch-check:: - Print the SHA1, type, and size of each object provided on stdin. May not be - combined with any other options or arguments. + Print the SHA1, type, and size of each object provided on stdin. May not + be combined with any other options or arguments. OUTPUT ------ diff --git a/Documentation/git-check-attr.txt b/Documentation/git-check-attr.txt index 8c2ac12f5d..50824e3a2d 100644 --- a/Documentation/git-check-attr.txt +++ b/Documentation/git-check-attr.txt @@ -14,7 +14,7 @@ SYNOPSIS DESCRIPTION ----------- -For every pathname, this command will list if each attr is 'unspecified', +For every pathname, this command will list if each attribute is 'unspecified', 'set', or 'unset' as a gitattribute on that pathname. OPTIONS @@ -23,11 +23,11 @@ OPTIONS Read file names from stdin instead of from the command-line. -z:: - Only meaningful with `--stdin`; paths are separated with - NUL character instead of LF. + Only meaningful with `--stdin`; paths are separated with a + NUL character instead of a linefeed character. \--:: - Interpret all preceding arguments as attributes, and all following + Interpret all preceding arguments as attributes and all following arguments as path names. If not supplied, only the first argument will be treated as an attribute. @@ -37,12 +37,12 @@ OUTPUT The output is of the form: <path> COLON SP <attribute> COLON SP <info> LF -Where <path> is the path of a file being queried, <attribute> is an attribute +<path> is the path of a file being queried, <attribute> is an attribute being queried and <info> can be either: 'unspecified';; when the attribute is not defined for the path. -'unset';; when the attribute is defined to false. -'set';; when the attribute is defined to true. +'unset';; when the attribute is defined as false. +'set';; when the attribute is defined as true. <value>;; when a value has been assigned to the attribute. EXAMPLES @@ -69,7 +69,7 @@ org/example/MyClass.java: diff: java org/example/MyClass.java: myAttr: set --------------- -* Listing attribute for multiple files: +* Listing an attribute for multiple files: --------------- $ git check-attr myAttr -- org/example/MyClass.java org/example/NoMyAttr.java org/example/MyClass.java: myAttr: set diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 034223cc5a..171b68377d 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -3,7 +3,7 @@ git-check-ref-format(1) NAME ---- -git-check-ref-format - Make sure ref name is well formed +git-check-ref-format - Ensures that a reference name is well formed SYNOPSIS -------- @@ -11,40 +11,40 @@ SYNOPSIS DESCRIPTION ----------- -Checks if a given 'refname' is acceptable, and exits non-zero if -it is not. +Checks if a given 'refname' is acceptable, and exits with a non-zero +status if it is not. A reference is used in git to specify branches and tags. A -branch head is stored under `$GIT_DIR/refs/heads` directory, and -a tag is stored under `$GIT_DIR/refs/tags` directory. git -imposes the following rules on how refs are named: +branch head is stored under the `$GIT_DIR/refs/heads` directory, and +a tag is stored under the `$GIT_DIR/refs/tags` directory. git +imposes the following rules on how references are named: -. It can include slash `/` for hierarchical (directory) +. They can include slash `/` for hierarchical (directory) grouping, but no slash-separated component can begin with a - dot `.`; + dot `.`. -. It cannot have two consecutive dots `..` anywhere; +. They cannot have two consecutive dots `..` anywhere. -. It cannot have ASCII control character (i.e. bytes whose +. They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 `DEL`), space, tilde `~`, caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`, - or open bracket `[` anywhere; + or open bracket `[` anywhere. -. It cannot end with a slash `/`. +. They cannot end with a slash `/`. -These rules makes it easy for shell script based tools to parse -refnames, pathname expansion by the shell when a refname is used +These rules make it easy for shell script based tools to parse +reference names, pathname expansion by the shell when a reference name is used unquoted (by mistake), and also avoids ambiguities in certain -refname expressions (see linkgit:git-rev-parse[1]). Namely: +reference name expressions (see linkgit:git-rev-parse[1]): -. double-dot `..` are often used as in `ref1..ref2`, and in some - context this notation means `{caret}ref1 ref2` (i.e. not in - ref1 and in ref2). +. A double-dot `..` is often used as in `ref1..ref2`, and in some + contexts this notation means `{caret}ref1 ref2` (i.e. not in + `ref1` and in `ref2`). -. tilde `~` and caret `{caret}` are used to introduce postfix +. A tilde `~` and caret `{caret}` are used to introduce the postfix 'nth parent' and 'peel onion' operation. -. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s +. A colon `:` is used as in `srcref:dstref` to mean "use srcref\'s value and store it in dstref" in fetch and push operations. It may also be used to select a specific object such as with 'git-cat-file': "git cat-file blob v1.3.3:refs.c". diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 5ece6cc805..132fc4faa5 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -137,6 +137,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-cherry.txt b/Documentation/git-cherry.txt index 74d14c4e7f..7deefdae8f 100644 --- a/Documentation/git-cherry.txt +++ b/Documentation/git-cherry.txt @@ -7,7 +7,7 @@ git-cherry - Find commits not merged upstream SYNOPSIS -------- -'git cherry' [-v] <upstream> [<head>] [<limit>] +'git cherry' [-v] [<upstream> [<head> [<limit>]]] DESCRIPTION ----------- @@ -51,6 +51,7 @@ OPTIONS <upstream>:: Upstream branch to compare against. + Defaults to the first tracked remote branch, if available. <head>:: Working branch; defaults to HEAD. diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 19a8917b83..6ab2af4b61 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -130,6 +130,10 @@ See also <<FILES>>. in the config file will cause the value to be multiplied by 1024, 1048576, or 1073741824 prior to output. +--bool-or-int:: + 'git-config' will ensure that the output matches the format of + either --bool or --int, as described above. + -z:: --null:: For all options that output values and/or keys, always diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index b7a8c10b87..8f9ba74c8b 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -62,7 +62,7 @@ OPTIONS -r <remote>:: The git remote to import this CVS repository into. Moves all CVS branches into remotes/<remote>/<branch> - akin to the 'git-clone' "--use-separate-remote" option. + akin to the way 'git-clone' uses 'origin' by default. -o <branch-for-HEAD>:: When no remote is specified (via -r) the 'HEAD' branch diff --git a/Documentation/git-daemon.txt b/Documentation/git-daemon.txt index d5596672a5..a85121c689 100644 --- a/Documentation/git-daemon.txt +++ b/Documentation/git-daemon.txt @@ -100,7 +100,7 @@ OPTIONS it takes for the server to process the sub-request and the time spent waiting for the next client's request. ---max-connections:: +--max-connections=n:: Maximum number of concurrent clients, defaults to 32. Set it to zero for no limit. diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt index a30c5ac966..b231dbb947 100644 --- a/Documentation/git-describe.txt +++ b/Documentation/git-describe.txt @@ -87,7 +87,7 @@ With something like git.git current tree, I get: v1.0.4-14-g2414721 i.e. the current head of my "parent" branch is based on v1.0.4, -but since it has a handful commits on top of that, +but since it has a few commits on top of that, describe has added the number of additional commits ("14") and an abbreviated object name for the commit itself ("2414721") at the end. diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index 68f97cd5ae..c1193953a1 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -125,6 +125,10 @@ You can use the 'map' convenience function in this filter, and other convenience functions, too. For example, calling 'skip_commit "$@"' will leave out the current commit (but not its changes! If you want that, use 'git-rebase' instead). ++ +You can also use the 'git_commit_non_empty_tree "$@"' instead of +'git commit-tree "$@"' if you don't wish to keep commits with a single parent +and that makes no change to the tree. --tag-name-filter <command>:: This is the filter for rewriting tag names. When passed, @@ -154,6 +158,16 @@ to other tags will be rewritten to point to the underlying commit. The result will contain that directory (and only that) as its project root. +--prune-empty:: + Some kind of filters will generate empty commits, that left the tree + untouched. This switch allow git-filter-branch to ignore such + commits. Though, this switch only applies for commits that have one + and only one parent, it will hence keep merges points. Also, this + option is not compatible with the use of '--commit-filter'. Though you + just need to use the function 'git_commit_non_empty_tree "$@"' instead + of the 'git commit-tree "$@"' idiom in your commit filter to make that + happen. + --original <namespace>:: Use this option to set the namespace where the original commits will be stored. The default value is 'refs/original'. @@ -201,6 +215,11 @@ git filter-branch --index-filter 'git rm --cached filename' HEAD Now, you will get the rewritten history saved in HEAD. +As with using `rm filename`, `git rm --cached filename` will fail +if the file is absent from the tree of a commit. If it is not important +whether the file is already absent from the tree, you can use +`git rm --cached --ignore-unmatch filename` instead. + To rewrite the repository to look as if `foodir/` had been its project root, and discard all other history: @@ -323,6 +342,47 @@ git filter-branch --index-filter \ --------------------------------------------------------------- + +Checklist for Shrinking a Repository +------------------------------------ + +git-filter-branch is often used to get rid of a subset of files, +usually with some combination of `\--index-filter` and +`\--subdirectory-filter`. People expect the resulting repository to +be smaller than the original, but you need a few more steps to +actually make it smaller, because git tries hard not to lose your +objects until you tell it to. First make sure that: + +* You really removed all variants of a filename, if a blob was moved + over its lifetime. `git log \--name-only \--follow \--all \-- + filename` can help you find renames. + +* You really filtered all refs: use `\--tag-name-filter cat \-- + \--all` when calling git-filter-branch. + +Then there are two ways to get a smaller repository. A safer way is +to clone, that keeps your original intact. + +* Clone it with `git clone +++file:///path/to/repo+++`. The clone + will not have the removed objects. See linkgit:git-clone[1]. (Note + that cloning with a plain path just hardlinks everything!) + +If you really don't want to clone it, for whatever reasons, check the +following points instead (in this order). This is a very destructive +approach, so *make a backup* or go back to cloning it. You have been +warned. + +* Remove the original refs backed up by git-filter-branch: say `git + for-each-ref \--format="%(refname)" refs/original/ | xargs -n 1 git + update-ref -d`. + +* Expire all reflogs with `git reflog expire \--expire=now \--all`. + +* Garbage collect all unreferenced objects with `git gc \--prune=now` + (or if your git-gc is not new enough to support arguments to + `\--prune`, use `git repack -ad; git prune` instead). + + Author ------ Written by Petr "Pasky" Baudis <pasky@suse.cz>, diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 1f577b8016..3c29655d75 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -92,7 +92,6 @@ include::diff-options.txt[] --numbered-files:: Output file names will be a simple number sequence without the default first line of the commit appended. - Mutually exclusive with the --stdout option. -k:: --keep-subject:: diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 7086eea74a..b292e9843a 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository SYNOPSIS -------- -'git gc' [--aggressive] [--auto] [--quiet] +'git gc' [--aggressive] [--auto] [--quiet] [--prune=<date> | --no-prune] DESCRIPTION ----------- @@ -59,6 +59,14 @@ are consolidated into a single pack by using the `-A` option of 'git-repack'. Setting `gc.autopacklimit` to 0 disables automatic consolidation of packs. +--prune=<date>:: + Prune loose objects older than date (default is 2 weeks ago, + overrideable by the config variable `gc.pruneExpire`). This + option is on by default. + +--no-prune:: + Do not prune any loose objects. + --quiet:: Suppress all progress reports. diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index bd49a0aee8..eed50572e0 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -51,7 +51,7 @@ imap.host:: imap.user:: The username to use when logging in to the server. -imap.password:: +imap.pass:: The password to use when logging in to the server. imap.port:: @@ -98,6 +98,20 @@ Using direct mode with SSL: .......................... +CAUTION +------- +It is still your responsibility to make sure that the email message +sent by your email program meets the standards of your project. +Many projects do not like patches to be attached. Some mail +agents will transform patches (e.g. wrap lines, send them as +format=flowed) in ways that make them fail. You will get angry +flames ridiculing you if you don't check this. + +Thunderbird in particular is known to be problematic. Thunderbird +users may wish to visit this web page for more information: + http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email + + BUGS ---- Doesn't handle lines starting with "From " in the message body. diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt index 71749c09d3..7151d12f34 100644 --- a/Documentation/git-init.txt +++ b/Documentation/git-init.txt @@ -54,15 +54,21 @@ is given: - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since the git group may be not the primary group of all users). + This is used to loosen the permissions of an otherwise safe umask(2) value. + Note that the umask still applies to the other permission bits (e.g. if + umask is '0022', using 'group' will not remove read privileges from other + (non-group) users). See '0xxx' for how to exactly specify the repository + permissions. - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository readable by all users. - - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx' - Any option except 'umask' can be set using this option. '0xxx' will - override users umask(2) value, and thus, users with a safe umask (0077) - can use this option. '0640' will create a repository which is group-readable - but not writable. '0660' is equivalent to 'group'. + - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx'. + '0xxx' will override users' umask(2) value (and not only loosen permissions + as 'group' and 'all' does). '0640' will create a repository which is + group-readable, but not group-writable or accessible to others. '0660' will + create a repo that is readable and writable to the current user and group, + but inaccessible to others. By default, the configuration flag receive.denyNonFastForwards is enabled in shared repositories, so that you cannot force a non fast-forwarding push diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 9f85d60b5f..057a021eb5 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -126,7 +126,7 @@ OPTIONS --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object - lines, show only handful hexdigits prefix. + lines, show only a partial prefix. Non default number of digits can be specified with --abbrev=<n>. \--:: diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index 237073103e..c3fdccb4c2 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -61,7 +61,7 @@ OPTIONS --abbrev[=<n>]:: Instead of showing the full 40-byte hexadecimal object - lines, show only handful hexdigits prefix. + lines, show only a partial prefix. Non default number of digits can be specified with --abbrev=<n>. --full-name:: diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 602e7c6d3b..5d3c632872 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -7,7 +7,7 @@ git-mergetool - Run merge conflict resolution tools to resolve merge conflicts SYNOPSIS -------- -'git mergetool' [--tool=<tool>] [<file>]... +'git mergetool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<file>]... DESCRIPTION ----------- @@ -22,7 +22,8 @@ with merge conflicts. OPTIONS ------- --t or --tool=<tool>:: +-t <tool>:: +--tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff @@ -60,6 +61,16 @@ variable `mergetool.<tool>.trustExitCode` can be set to `true`. Otherwise, 'git-mergetool' will prompt the user to indicate the success of the resolution after the custom tool has exited. +-y:: +--no-prompt:: + Don't prompt before each invocation of the merge resolution + program. + +--prompt:: + Prompt before each invocation of the merge resolution program. + This is the default behaviour; the option is provided to + override any configuration settings. + Author ------ Written by Theodore Y Ts'o <tytso@mit.edu> diff --git a/Documentation/git-pack-refs.txt b/Documentation/git-pack-refs.txt index a5244d35f4..1ee99c208c 100644 --- a/Documentation/git-pack-refs.txt +++ b/Documentation/git-pack-refs.txt @@ -26,7 +26,7 @@ problem by stashing the refs in a single file, traditional `$GIT_DIR/refs` hierarchy, it is looked up in this file and used if found. -Subsequent updates to branches always creates new file under +Subsequent updates to branches always create new files under `$GIT_DIR/refs` hierarchy. A recommended practice to deal with a repository with too many @@ -35,7 +35,7 @@ occasionally run `git pack-refs \--prune`. Tags are by definition stationary and are not expected to change. Branch heads will be packed with the initial `pack-refs --all`, but only the currently active branch heads will become unpacked, -and next `pack-refs` (without `--all`) will leave them +and the next `pack-refs` (without `--all`) will leave them unpacked. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index ac6421178c..4e7e5a719a 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -48,17 +48,19 @@ 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. + -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. +The object referenced by <src> is used to update the <dst> reference +on the remote side, but by default this is only allowed if the +update can fast forward <dst>. By having the optional leading `{plus}`, +you can tell git to update the <dst> ref even when the update is not a +fast forward. This does *not* attempt to merge <src> into <dst>. See +EXAMPLES below for details. + `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. + -The special refspec `:` (or `+:` to allow non-fast forward updates) +The special refspec `:` (or `{plus}:` to allow non-fast forward updates) directs git to push "matching" branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side. This is the default operation mode @@ -218,6 +220,30 @@ git push origin :experimental:: Find a ref that matches `experimental` in the `origin` repository (e.g. `refs/heads/experimental`), and delete it. +git push origin {plus}dev:master:: + Update the origin repository's master branch with the dev branch, + allowing non-fast forward updates. *This can leave unreferenced + commits dangling in the origin repository.* Consider the + following situation, where a fast forward is not possible: ++ +---- + o---o---o---A---B origin/master + \ + X---Y---Z dev +---- ++ +The above command would change the origin repository to ++ +---- + A---B (unnamed branch) + / + o---o---o---X---Y---Z master +---- ++ +Commits A and B would no longer belong to a branch with a symbolic name, +and so would be unreachable. As such, these commits would be removed by +a `git gc` command on the origin repository. + Author ------ diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index c8ad86a56f..da3c38cd60 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] - [-s <strategy> | --strategy=<strategy>] [--no-verify] - [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges] - [--onto <newbase>] <upstream> [<branch>] +'git rebase' [-i | --interactive] [options] [--onto <newbase>] + <upstream> [<branch>] +'git rebase' [-i | --interactive] [options] --onto <newbase> + --root [<branch>] + 'git rebase' --continue | --skip | --abort DESCRIPTION @@ -22,7 +23,8 @@ it remains on the current branch. All changes made by commits in the current branch but that are not in <upstream> are saved to a temporary area. This is the same set -of commits that would be shown by `git log <upstream>..HEAD`. +of commits that would be shown by `git log <upstream>..HEAD` (or +`git log HEAD`, if --root is specified). The current branch is reset to <upstream>, or <newbase> if the --onto option was supplied. This has the exact same effect as @@ -241,9 +243,10 @@ OPTIONS context exist they all must match. By default no context is ever ignored. ---whitespace=<nowarn|warn|error|error-all|strip>:: +--whitespace=<option>:: This flag is passed to the 'git-apply' program (see linkgit:git-apply[1]) that applies the patch. + Incompatible with the --interactive option. -i:: --interactive:: @@ -255,6 +258,15 @@ OPTIONS --preserve-merges:: Instead of ignoring merges, try to recreate them. +--root:: + Rebase all commits reachable from <branch>, instead of + limiting them with an <upstream>. This allows you to rebase + the root commit(s) on a branch. Must be used with --onto, and + will skip changes already contained in <newbase> (instead of + <upstream>). When used together with --preserve-merges, 'all' + root commits will be rewritten to have <newbase> as parent + instead. + include::merge-strategies.txt[] NOTES diff --git a/Documentation/git-reset.txt b/Documentation/git-reset.txt index 2049f3d97b..abb25d1c00 100644 --- a/Documentation/git-reset.txt +++ b/Documentation/git-reset.txt @@ -8,7 +8,7 @@ git-reset - Reset current HEAD to the specified state SYNOPSIS -------- [verse] -'git reset' [--mixed | --soft | --hard] [-q] [<commit>] +'git reset' [--mixed | --soft | --hard | --merge] [-q] [<commit>] 'git reset' [-q] [<commit>] [--] <paths>... DESCRIPTION @@ -45,6 +45,11 @@ OPTIONS switched to. Any changes to tracked files in the working tree since <commit> are lost. +--merge:: + Resets the index to match the tree recorded by the named commit, + and updates the files that are different between the named commit + and the current commit in the working tree. + -q:: Be quiet, only report errors. @@ -152,6 +157,28 @@ tip of the current branch in ORIG_HEAD, so resetting hard to it brings your index file and the working tree back to that state, and resets the tip of the branch to that commit. +Undo a merge or pull inside a dirty work tree:: ++ +------------ +$ git pull <1> +Auto-merging nitfol +Merge made by recursive. + nitfol | 20 +++++---- + ... +$ git reset --merge ORIG_HEAD <2> +------------ ++ +<1> Even if you may have local modifications in your +working tree, you can safely say "git pull" when you know +that the change in the other branch does not overlap with +them. +<2> After inspecting the result of the merge, you may find +that the change in the other branch is unsatisfactory. Running +"git reset --hard ORIG_HEAD" will let you go back to where you +were, but it will discard your local changes, which you do not +want. "git reset --merge" keeps your local changes. + + Interrupted workflow:: + Suppose you are interrupted by an urgent fix request while you diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 2921da320d..3ccef2f2b3 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-send-email.txt b/Documentation/git-send-email.txt index 66bf3b2fcd..fc0a4ab441 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -177,14 +177,25 @@ Automating --suppress-cc:: Specify an additional category of recipients to suppress the - auto-cc of. 'self' will avoid including the sender, 'author' will - avoid including the patch author, 'cc' will avoid including anyone - mentioned in Cc lines in the patch, 'sob' will avoid including - anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid - running the --cc-cmd. 'all' will suppress all auto cc values. - Default is the value of 'sendemail.suppresscc' configuration value; - if that is unspecified, default to 'self' if --suppress-from is - specified, as well as 'sob' if --no-signed-off-cc is specified. + auto-cc of: ++ +-- +- 'author' will avoid including the patch author +- 'self' will avoid including the sender +- 'cc' will avoid including anyone mentioned in Cc lines in the patch header + except for self (use 'self' for that). +- 'ccbody' will avoid including anyone mentioned in Cc lines in the + patch body (commit message) except for self (use 'self' for that). +- 'sob' will avoid including anyone mentioned in Signed-off-by lines except + for self (use 'self' for that). +- 'cccmd' will avoid running the --cc-cmd. +- 'body' is equivalent to 'sob' + 'ccbody' +- 'all' will suppress all auto cc values. +-- ++ +Default is the value of 'sendemail.suppresscc' configuration value; if +that is unspecified, default to 'self' if --suppress-from is +specified, as well as 'body' if --no-signed-off-cc is specified. --[no-]suppress-from:: If this is set, do not add the From: address to the cc: list. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index 8f7c0e226d..42463a955d 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -45,45 +45,16 @@ OPTIONS and subsequent lines are indented by `indent2` spaces. `width`, `indent1`, and `indent2` default to 76, 6 and 9 respectively. -FILES ------ - -If a file `.mailmap` exists at the toplevel of the repository, -it is used to map an author email address to a canonical real name. This -can be used to coalesce together commits by the same person where their -name was spelled differently (whether with the same email address or -not). - -Each line in the file consists, in this order, of the canonical real name -of an author, whitespace, and an email address (enclosed by '<' and '>') -to map to the name. Use hash '#' for comments, either on their own line, -or after the email address. - -A canonical name may appear in more than one line, associated with -different email addresses, but it doesn't make sense for a given address -to appear more than once (if that happens, a later line overrides the -earlier ones). - -So, for example, if your history contains commits by two authors, Jane -and Joe, whose names appear in the repository under several forms: - ------------- -Joe Developer <joe@example.com> -Joe R. Developer <joe@example.com> -Jane Doe <jane@example.com> -Jane Doe <jane@laptop.(none)> -Jane D. <jane@desktop.(none)> ------------- - -Then, supposing Joe wants his middle name initial used, and Jane prefers -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> ------------- + +MAPPING AUTHORS +--------------- + +The `.mailmap` feature is used to coalesce together commits by the same +person in the shortlog, where their name and/or email address was +spelled differently. + +include::mailmap.txt[] + Author ------ diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 8277577a6f..7e9ff3762b 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-submodule.txt b/Documentation/git-submodule.txt index 2f207fbbda..3b8df44673 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git submodule' [--quiet] add [-b branch] [--] <repository> <path> 'git submodule' [--quiet] status [--cached] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] -'git submodule' [--quiet] update [--init] [--] [<path>...] +'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [<path>...] 'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...] 'git submodule' [--quiet] foreach <command> 'git submodule' [--quiet] sync [--] [<path>...] @@ -172,6 +172,11 @@ OPTIONS (the default). This limit only applies to modified submodules. The size is always limited to 1 for added/deleted/typechanged submodules. +-N:: +--no-fetch:: + This option is only valid for the update command. + Don't fetch new objects from the remote site. + <path>...:: Paths to submodule(s). When specified this will restrict the command to only operate on the submodules found at the specified paths. diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index 8d0c421b80..cda3389331 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -92,6 +92,30 @@ COMMANDS .git/config file may be specified as an optional command-line argument. +--localtime;; + Store Git commit times in the local timezone instead of UTC. This + makes 'git-log' (even without --date=local) show the same times + that `svn log` would in the local timezone. + +This doesn't interfere with interoperating with the Subversion +repository you cloned from, but if you wish for your local Git +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; @@ -145,6 +169,10 @@ and have no uncommitted changes. reused if a user is later given access to an alternate transport method (e.g. `svn+ssh://` or `https://`) for commit. +config key: svn-remote.<name>.commiturl + +config key: svn.commiturl (overwrites all svn-remote.<name>.commiturl options) + Using this option for any other purpose (don't ask) is very strongly discouraged. -- @@ -475,6 +503,14 @@ svn-remote.<name>.rewriteRoot:: the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. +svn.brokenSymlinkWorkaround:: +This disables potentially expensive checks to workaround broken symlinks +checked into SVN by broken clients. Set this option to "false" if you +track a SVN repository with many empty blobs that are not symlinks. +This option may be changed while "git-svn" is running and take effect on +the next revision fetched. If unset, git-svn assumes this option to be +"true". + -- Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 3546acffb5..fa733214ab 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 @@ -69,6 +69,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 17dc8b2019..9a26bde73e 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,9 +43,17 @@ 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.2/git.html[documentation for release 1.6.2] * release notes for + link:RelNotes-1.6.2.txt[1.6.2]. + +* link:v1.6.1.3/git.html[documentation for release 1.6.1.3] + +* release notes for + link:RelNotes-1.6.1.3.txt[1.6.1.3], + link:RelNotes-1.6.1.2.txt[1.6.1.2], + 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 82c10fa0fd..aaa073efc8 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -18,10 +18,10 @@ A `gitattributes` file is a simple text file that gives Each line in `gitattributes` file is of form: - glob attr1 attr2 ... + pattern attr1 attr2 ... -That is, a glob pattern followed by an attributes list, -separated by whitespaces. When the glob pattern matches the +That is, a pattern followed by an attributes list, +separated by whitespaces. When the pattern matches the path in question, the attributes listed on the line are given to the path. @@ -48,13 +48,14 @@ Set to a value:: Unspecified:: - No glob pattern matches the path, and nothing says if + No pattern matches the path, and nothing says if the path has or does not have the attribute, the attribute for the path is said to be Unspecified. -When more than one glob pattern matches the path, a later line +When more than one pattern matches the path, a later line overrides an earlier line. This overriding is done per -attribute. +attribute. The rules how the pattern matches paths are the +same as in `.gitignore` files; see linkgit:gitignore[5]. When deciding what attributes are assigned to a path, git consults `$GIT_DIR/info/attributes` file (which has the highest @@ -296,7 +297,8 @@ for paths. Then, you would define a "diff.tex.xfuncname" configuration to specify a regular expression that matches a line that you would -want to appear as the hunk header "TEXT", like this: +want to appear as the hunk header "TEXT". Add a section to your +`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this: ------------------------ [diff "tex"] @@ -317,6 +319,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 +338,26 @@ 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 in your +`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this: + +------------------------ +[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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -351,7 +375,8 @@ resulting text on stdout. For example, to show the diff of the exif information of a file instead of the binary information (assuming you have the -exif tool installed): +exif tool installed), add the following section to your +`$GIT_DIR/config` file (or `$HOME/.gitconfig` file): ------------------------ [diff "jpg"] diff --git a/Documentation/gitcore-tutorial.txt b/Documentation/gitcore-tutorial.txt index e4dd5518c8..7ba5e589d7 100644 --- a/Documentation/gitcore-tutorial.txt +++ b/Documentation/gitcore-tutorial.txt @@ -1243,10 +1243,10 @@ $ git ls-files --stage ------------ In our example of only two files, we did not have unchanged -files so only 'example' resulted in collapsing, but in real-life -large projects, only small number of files change in one commit, -and this 'collapsing' tends to trivially merge most of the paths -fairly quickly, leaving only a handful the real changes in non-zero +files so only 'example' resulted in collapsing. But in real-life +large projects, when only a small number of files change in one commit, +this 'collapsing' tends to trivially merge most of the paths +fairly quickly, leaving only a handful of real changes in non-zero stages. To look at only non-zero stages, use `\--unmerged` flag: diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt index aaa7ef737a..0e49c1c037 100644 --- a/Documentation/gitcvs-migration.txt +++ b/Documentation/gitcvs-migration.txt @@ -118,7 +118,7 @@ Importing a CVS archive First, install version 2.1 or higher of cvsps from link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make sure it is in your path. Then cd to a checked out CVS working directory -of the project you are interested in and run 'git-cvsimport': +of the project you are interested in and run linkgit:git-cvsimport[1]: ------------------------------------------- $ git cvsimport -C <destination> <module> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 28a8abcf52..1fd512bca2 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -15,7 +15,7 @@ DESCRIPTION Hooks are little scripts you can place in `$GIT_DIR/hooks` directory to trigger action at certain points. When -'git-init' is run, a handful example hooks are copied in the +'git-init' is run, a handful of example hooks are copied into the `hooks` directory of the new repository, but by default they are all disabled. To enable a hook, rename it by removing its `.sample` suffix. diff --git a/Documentation/gitk.txt b/Documentation/gitk.txt index 4673a75a98..cf465cb47e 100644 --- a/Documentation/gitk.txt +++ b/Documentation/gitk.txt @@ -47,7 +47,8 @@ frequently used options. After an attempt to merge stops with conflicts, show the commits on the history between two branches (i.e. the HEAD and the MERGE_HEAD) - that modify the conflicted files. + that modify the conflicted files and do not exist on all the heads + being merged. --argscmd=<command>:: Command to be run each time gitk has to determine the list of @@ -73,7 +74,7 @@ frequently used options. <path>...:: Limit commits to the ones touching files in the given paths. Note, to - avoid ambiguity wrt. revision names use "--" to separate the paths + avoid ambiguity with respect to revision names use "--" to separate the paths from any preceding options. Examples diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt index a057b50b2b..dc8fc3a18a 100644 --- a/Documentation/gittutorial-2.txt +++ b/Documentation/gittutorial-2.txt @@ -32,12 +32,12 @@ Initialized empty Git repository in .git/ $ echo 'hello world' > file.txt $ git add . $ git commit -a -m "initial commit" -[master (root-commit)] created 54196cc: "initial commit" +[master (root-commit) 54196cc] initial commit 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 file.txt $ echo 'hello world!' >file.txt $ git commit -a -m "add emphasis" -[master] created c4d59f3: "add emphasis" +[master c4d59f3] add emphasis 1 files changed, 1 insertions(+), 1 deletions(-) ------------------------------------------------ diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index 458fafdb2c..c5d5596d89 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/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt index d214d4bf9d..74a1c0c4ba 100644 --- a/Documentation/howto/rebase-from-internal-branch.txt +++ b/Documentation/howto/rebase-from-internal-branch.txt @@ -27,7 +27,7 @@ the kind of task StGIT is designed to do. I just have done a simpler one, this time using only the core GIT tools. -I had a handful commits that were ahead of master in pu, and I +I had a handful of commits that were ahead of master in pu, and I wanted to add some documentation bypassing my usual habit of placing new things in pu first. At the beginning, the commit ancestry graph looked like this: diff --git a/Documentation/howto/revert-a-faulty-merge.txt b/Documentation/howto/revert-a-faulty-merge.txt index 39b1da440a..3b4a390005 100644 --- a/Documentation/howto/revert-a-faulty-merge.txt +++ b/Documentation/howto/revert-a-faulty-merge.txt @@ -39,7 +39,7 @@ Such a "revert" of a merge can be made with: $ git revert -m 1 M -After the develpers of the side branch fixes their mistakes, the history +After the developers of the side branch fix their mistakes, the history may look like this: ---o---o---o---M---x---x---W---x @@ -116,7 +116,7 @@ If you reverted the revert in such a case as in the previous example: / \ / ---A---B A'--B'--C' -where Y is the revert of W, A' and B'are rerolled A and B, and there may +where Y is the revert of W, A' and B' are rerolled A and B, and there may also be a further fix-up C' on the side branch. "diff Y^..Y" is similar to "diff -R W^..W" (which in turn means it is similar to "diff M^..M"), and "diff A'^..C'" by definition would be similar but different from that, diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt index 4032748608..622ee5c8dd 100644 --- a/Documentation/howto/setup-git-server-over-http.txt +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -143,7 +143,7 @@ Then, add something like this to your httpd.conf Require valid-user </Location> - Debian automatically reads all files under /etc/apach2/conf.d. + Debian automatically reads all files under /etc/apache2/conf.d. The password file can be somewhere else, but it has to be readable by Apache and preferably not readable by the world. diff --git a/Documentation/mailmap.txt b/Documentation/mailmap.txt new file mode 100644 index 0000000000..288f04e70c --- /dev/null +++ b/Documentation/mailmap.txt @@ -0,0 +1,74 @@ +If the file `.mailmap` exists at the toplevel of the repository, or at +the location pointed to by the mailmap.file configuration option, it +is used to map author and committer names and email addresses to +canonical real names and email addresses. + +In the simple form, each line in the file consists of the canonical +real name of an author, whitespace, and an email address used in the +commit (enclosed by '<' and '>') to map to the name. For example: +-- + Proper Name <commit@email.xx> +-- + +The more complex forms are: +-- + <proper@email.xx> <commit@email.xx> +-- +which allows mailmap to replace only the email part of a commit, and: +-- + Proper Name <proper@email.xx> <commit@email.xx> +-- +which allows mailmap to replace both the name and the email of a +commit matching the specified commit email address, and: +-- + Proper Name <proper@email.xx> Commit Name <commit@email.xx> +-- +which allows mailmap to replace both the name and the email of a +commit matching both the specified commit name and email address. + +Example 1: Your history contains commits by two authors, Jane +and Joe, whose names appear in the repository under several forms: + +------------ +Joe Developer <joe@example.com> +Joe R. Developer <joe@example.com> +Jane Doe <jane@example.com> +Jane Doe <jane@laptop.(none)> +Jane D. <jane@desktop.(none)> +------------ + +Now suppose that Joe wants his middle name initial used, and Jane +prefers her family name fully spelled out. A proper `.mailmap` file +would look like: + +------------ +Jane Doe <jane@desktop.(none)> +Joe R. Developer <joe@example.com> +------------ + +Note how there is no need for an entry for <jane@laptop.(none)>, because the +real name of that author is already correct. + +Example 2: Your repository contains commits from the following +authors: + +------------ +nick1 <bugs@company.xx> +nick2 <bugs@company.xx> +nick2 <nick2@company.xx> +santa <me@company.xx> +claus <me@company.xx> +CTO <cto@coompany.xx> +------------ + +Then you might want a `.mailmap` file that looks like: +------------ +<cto@company.xx> <cto@coompany.xx> +Some Dude <some@dude.xx> nick1 <bugs@company.xx> +Other Author <other@author.xx> nick2 <bugs@company.xx> +Other Author <other@author.xx> <nick2@company.xx> +Santa Claus <santa.claus@northpole.xx> <me@company.xx> +------------ + +Use hash '#' for comments that are either on their own line, or after +the email address. diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 0a8a948e6f..159390c35a 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -101,16 +101,18 @@ The placeholders are: - '%P': parent hashes - '%p': abbreviated parent hashes - '%an': author name -- '%aN': author name (respecting .mailmap) +- '%aN': author name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) - '%ae': author email +- '%aE': author email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) - '%ad': author date (format respects --date= option) - '%aD': author date, RFC2822 style - '%ar': author date, relative - '%at': author date, UNIX timestamp - '%ai': author date, ISO 8601 format - '%cn': committer name -- '%cN': committer name (respecting .mailmap) +- '%cN': committer name (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) - '%ce': committer email +- '%cE': committer email (respecting .mailmap, see linkgit:git-shortlog[1] or linkgit:git-blame[1]) - '%cd': committer date - '%cD': committer date, RFC2822 style - '%cr': committer date, relative @@ -124,6 +126,7 @@ The placeholders are: - '%Cgreen': switch color to green - '%Cblue': switch color to blue - '%Creset': reset color +- '%C(...)': color specification, as described in color.branch.* config option - '%m': left, right or boundary mark - '%n': newline - '%x00': print a byte from a hex code diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 6d66c74cc1..5f21efe407 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -10,7 +10,7 @@ configuration (see linkgit:git-config[1]). --abbrev-commit:: Instead of showing the full 40-byte hexadecimal commit object - name, show only handful hexdigits prefix. Non default number of + name, show only a partial prefix. Non default number of digits can be specified with "--abbrev=<n>" (which also modifies diff output, if it is displayed). + diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index b9f6e4d1b7..7dd237c2f6 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -13,7 +13,7 @@ include::pretty-options.txt[] Synonym for `--date=relative`. ---date={relative,local,default,iso,rfc,short}:: +--date={relative,local,default,iso,rfc,short,raw}:: Only takes effect for dates shown in human-readable format, such as when using "--pretty". `log.date` config variable sets a default @@ -31,6 +31,8 @@ format, often found in E-mail messages. + `--date=short` shows only date but not time, in `YYYY-MM-DD` format. + +`--date=raw` shows the date in the internal raw git format `%s %z` format. ++ `--date=default` shows timestamps in the original timezone (either committer's or author's). @@ -566,11 +568,11 @@ This outputs all the commit objects between the included and excluded commits, ordered by their distance to the included and excluded commits. The farthest from them is displayed first. (This is the only one displayed by `--bisect`.) - ++ This is useful because it makes it easy to choose a good commit to test when you want to avoid to test some of them for some reason (they may not compile for example). - ++ This option can be used along with `--bisect-vars`, in this case, after all the sorted commit objects, there will be the same text as if `--bisect-vars` had been used alone. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index 82e9e831b6..2efe7a40be 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -52,6 +52,21 @@ Functions Wait for the completion of an asynchronous function that was started with start_async(). +`run_hook`:: + + Run a hook. + The first argument is a pathname to an index file, or NULL + if the hook uses the default index file or no index is needed. + The second argument is the name of the hook. + The further arguments correspond to the hook arguments. + The last argument has to be NULL to terminate the arguments list. + If the hook does not exist or is not executable, the return + value will be zero. + If it is executable, the hook will be executed and the exit + status of the hook is returned. + On execution, .stdout_to_stderr and .no_stdin will be set. + (See below.) + Data structures --------------- diff --git a/Documentation/technical/api-strbuf.txt b/Documentation/technical/api-strbuf.txt index 985800e43a..7438149249 100644 --- a/Documentation/technical/api-strbuf.txt +++ b/Documentation/technical/api-strbuf.txt @@ -133,8 +133,10 @@ Functions * Adding data to the buffer -NOTE: All of these functions in this section will grow the buffer as - necessary. +NOTE: All of the functions in this section will grow the buffer as necessary. +If they fail for some reason other than memory shortage and the buffer hadn't +been allocated before (i.e. the `struct strbuf` was set to `STRBUF_INIT`), +then they will free() it. `strbuf_addch`:: @@ -220,7 +222,7 @@ which can be used by the programmer of the callback as she sees fit. Read a given size of data from a FILE* pointer to the buffer. + -NOTE: The buffer is rewinded if the read fails. If -1 is returned, +NOTE: The buffer is rewound if the read fails. If -1 is returned, `errno` must be consulted, like you would do for `read(3)`. `strbuf_read()`, `strbuf_read_file()` and `strbuf_getline()` has the same behaviour as well. @@ -235,6 +237,11 @@ same behaviour as well. Read the contents of a file, specified by its path. The third argument can be used to give a hint about the file size, to avoid reallocs. +`strbuf_readlink`:: + + Read the target of a symbolic link, specified by its path. The third + argument can be used to give a hint about the size, to avoid reallocs. + `strbuf_getline`:: Read a line from a FILE* pointer. The second argument specifies the line diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index e5e62ef0de..9d65aed417 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.1.4 +DEF_VER=v1.6.2.5 LF=' ' @@ -101,6 +101,9 @@ Issues of note: Building and installing the info file additionally requires makeinfo and docbook2X. Version 0.8.3 is known to work. + Building and installing the pdf file additionally requires + dblatex. Version 0.2.7 with asciidoc >= 8.2.7 is known to work. + The documentation is written for AsciiDoc 7, but "make ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8. @@ -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 @@ -221,7 +228,7 @@ GITWEB_FAVICON = git-favicon.png GITWEB_SITE_HEADER = GITWEB_SITE_FOOTER = -export prefix bindir sharedir htmldir sysconfdir +export prefix bindir sharedir sysconfdir CC = gcc AR = ar @@ -289,7 +296,6 @@ EXTRA_PROGRAMS = # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS += $(EXTRA_PROGRAMS) PROGRAMS += git-fast-import$X -PROGRAMS += git-fetch-pack$X PROGRAMS += git-hash-object$X PROGRAMS += git-index-pack$X PROGRAMS += git-merge-index$X @@ -298,7 +304,6 @@ PROGRAMS += git-mktag$X PROGRAMS += git-mktree$X PROGRAMS += git-pack-redundant$X PROGRAMS += git-patch-id$X -PROGRAMS += git-send-pack$X PROGRAMS += git-shell$X PROGRAMS += git-show-index$X PROGRAMS += git-unpack-file$X @@ -310,8 +315,8 @@ PROGRAMS += git-var$X # builtin-$C.o but is linked in as part of some other command. BUILT_INS += $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) -BUILT_INS += git-cherry-pick$X BUILT_INS += git-cherry$X +BUILT_INS += git-cherry-pick$X BUILT_INS += git-format-patch$X BUILT_INS += git-fsck-objects$X BUILT_INS += git-get-tar-commit-id$X @@ -350,8 +355,8 @@ LIB_H += builtin.h LIB_H += cache.h LIB_H += cache-tree.h LIB_H += commit.h -LIB_H += compat/mingw.h LIB_H += compat/cygwin.h +LIB_H += compat/mingw.h LIB_H += csum-file.h LIB_H += decorate.h LIB_H += delta.h @@ -376,7 +381,6 @@ LIB_H += pack-refs.h LIB_H += pack-revindex.h LIB_H += parse-options.h LIB_H += patch-ids.h -LIB_H += string-list.h LIB_H += pkt-line.h LIB_H += progress.h LIB_H += quote.h @@ -388,7 +392,9 @@ 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 += string-list.h LIB_H += tag.h LIB_H += transport.h LIB_H += tree.h @@ -427,8 +433,8 @@ LIB_OBJS += diffcore-order.o LIB_OBJS += diffcore-pickaxe.o LIB_OBJS += diffcore-rename.o LIB_OBJS += diff-delta.o -LIB_OBJS += diff-no-index.o LIB_OBJS += diff-lib.o +LIB_OBJS += diff-no-index.o LIB_OBJS += diff.o LIB_OBJS += dir.o LIB_OBJS += editor.o @@ -460,9 +466,9 @@ LIB_OBJS += pager.o LIB_OBJS += parse-options.o LIB_OBJS += patch-delta.o LIB_OBJS += patch-ids.o -LIB_OBJS += string-list.o LIB_OBJS += path.o LIB_OBJS += pkt-line.o +LIB_OBJS += preload-index.o LIB_OBJS += pretty.o LIB_OBJS += progress.o LIB_OBJS += quote.o @@ -476,12 +482,14 @@ LIB_OBJS += revision.o LIB_OBJS += run-command.o LIB_OBJS += server-info.o LIB_OBJS += setup.o -LIB_OBJS += sha1_file.o LIB_OBJS += sha1-lookup.o +LIB_OBJS += sha1_file.o LIB_OBJS += sha1_name.o LIB_OBJS += shallow.o LIB_OBJS += sideband.o +LIB_OBJS += sigchain.o LIB_OBJS += strbuf.o +LIB_OBJS += string-list.o LIB_OBJS += symlinks.o LIB_OBJS += tag.o LIB_OBJS += trace.o @@ -490,8 +498,8 @@ LIB_OBJS += tree-diff.o LIB_OBJS += tree.o LIB_OBJS += tree-walk.o LIB_OBJS += unpack-trees.o -LIB_OBJS += userdiff.o LIB_OBJS += usage.o +LIB_OBJS += userdiff.o LIB_OBJS += utf8.o LIB_OBJS += walker.o LIB_OBJS += wrapper.o @@ -499,7 +507,6 @@ LIB_OBJS += write_or_die.o LIB_OBJS += ws.o LIB_OBJS += wt-status.o LIB_OBJS += xdiff-interface.o -LIB_OBJS += preload-index.o BUILTIN_OBJS += builtin-add.o BUILTIN_OBJS += builtin-annotate.o @@ -640,10 +647,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 +794,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 +803,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 +824,7 @@ ifeq ($(uname_S),Darwin) BASIC_LDFLAGS += -L/opt/local/lib endif endif + PTHREAD_LIBS = endif ifndef CC_LD_DYNPATH @@ -849,7 +857,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 +1040,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 +1102,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 +1268,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 +1309,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 @@ -1307,6 +1329,9 @@ html: info: $(MAKE) -C Documentation info +pdf: + $(MAKE) -C Documentation pdf + TAGS: $(RM) TAGS $(FIND) . -name '*.[hcS]' -print | xargs etags -a @@ -1353,7 +1378,17 @@ endif ### Testing rules -TEST_PROGRAMS = test-chmtime$X test-dump-cache-tree$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-parse-options$X test-path-utils$X +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) @@ -1366,6 +1401,8 @@ export NO_SVN_TESTS test: all $(MAKE) -C t/ all +test-ctype$X: ctype.o + test-date$X: date.o ctype.o test-delta$X: diff-delta.o patch-delta.o @@ -1397,17 +1434,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 @@ -1429,12 +1466,14 @@ endif bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ { $(RM) "$$execdir/git-add$X" && \ - ln git-add$X "$$execdir/git-add$X" 2>/dev/null || \ - cp git-add$X "$$execdir/git-add$X"; } && \ - { $(foreach p,$(filter-out git-add$X,$(BUILT_INS)), $(RM) "$$execdir/$p" && \ - ln "$$execdir/git-add$X" "$$execdir/$p" 2>/dev/null || \ - ln -s "git-add$X" "$$execdir/$p" 2>/dev/null || \ - cp "$$execdir/git-add$X" "$$execdir/$p" || exit;) } && \ + ln "$$bindir/git$X" "$$execdir/git-add$X" 2>/dev/null || \ + cp "$$bindir/git$X" "$$execdir/git-add$X"; } && \ + { for p in $(filter-out git-add$X,$(BUILT_INS)); do \ + $(RM) "$$execdir/$$p" && \ + ln "$$execdir/git-add$X" "$$execdir/$$p" 2>/dev/null || \ + ln -s "git-add$X" "$$execdir/$$p" 2>/dev/null || \ + cp "$$execdir/git-add$X" "$$execdir/$$p" || exit; \ + done } && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" install-doc: @@ -1449,6 +1488,9 @@ install-html: install-info: $(MAKE) -C Documentation install-info +install-pdf: + $(MAKE) -C Documentation install-pdf + quick-install-doc: $(MAKE) -C Documentation quick-install @@ -24,10 +24,18 @@ It was originally written by Linus Torvalds with help of a group of hackers around the net. It is currently maintained by Junio C Hamano. Please read the file INSTALL for installation instructions. + See Documentation/gittutorial.txt to get started, then see -Documentation/everyday.txt for a useful minimum set of commands, -and "man git-commandname" for documentation of each command. -CVS users may also want to read Documentation/cvs-migration.txt. +Documentation/everyday.txt for a useful minimum set of commands, and +Documentation/git-commandname.txt for documentation of each command. +If git has been correctly installed, then the tutorial can also be +read with "man gittutorial" or "git help tutorial", and the +documentation of each command with "man git-commandname" or "git help +commandname". + +CVS users may also want to read Documentation/gitcvs-migration.txt +("man gitcvs-migration" or "git help cvs-migration" if git is +installed). Many Git online resources are accessible from http://git.or.cz/ including full documentation and Git related tools. @@ -1 +1 @@ -Documentation/RelNotes-1.6.1.4.txt
\ No newline at end of file +Documentation/RelNotes-1.6.2.5.txt
\ No newline at end of file @@ -132,7 +132,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base, err = write_entry(args, sha1, path.buf, path.len, mode, NULL, 0); if (err) return err; - return READ_TREE_RECURSIVE; + return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); } buffer = sha1_file_to_archive(path_without_prefix, sha1, mode, @@ -103,14 +103,22 @@ void create_branch(const char *head, struct ref_lock *lock; struct commit *commit; unsigned char sha1[20]; - char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20]; + char *real_ref, msg[PATH_MAX + 20]; + struct strbuf ref = STRBUF_INIT; int forcing = 0; + int len; - snprintf(ref, sizeof ref, "refs/heads/%s", name); - if (check_ref_format(ref)) + len = strlen(name); + if (interpret_nth_last_branch(name, &ref) != len) { + strbuf_reset(&ref); + strbuf_add(&ref, name, len); + } + strbuf_splice(&ref, 0, 0, "refs/heads/", 11); + + if (check_ref_format(ref.buf)) die("'%s' is not a valid branch name.", name); - if (resolve_ref(ref, sha1, 1, NULL)) { + if (resolve_ref(ref.buf, sha1, 1, NULL)) { if (!force) die("A branch named '%s' already exists.", name); else if (!is_bare_repository() && !strcmp(head, name)) @@ -142,7 +150,7 @@ void create_branch(const char *head, die("Not a valid branch point: '%s'.", start_name); hashcpy(sha1, commit->object.sha1); - lock = lock_any_ref_for_update(ref, NULL, 0); + lock = lock_any_ref_for_update(ref.buf, NULL, 0); if (!lock) die("Failed to lock ref for update: %s.", strerror(errno)); @@ -162,6 +170,7 @@ void create_branch(const char *head, if (write_ref_sha1(lock, sha1, msg) < 0) die("Failed to write ref: %s.", strerror(errno)); + strbuf_release(&ref); free(real_ref); } diff --git a/builtin-add.c b/builtin-add.c index ac98c8354d..08443f2f1e 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -15,7 +15,7 @@ static const char * const builtin_add_usage[] = { "git add [options] [--] <filepattern>...", NULL }; -static int patch_interactive = 0, add_interactive = 0; +static int patch_interactive, add_interactive; static int take_worktree_changes; static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) diff --git a/builtin-apply.c b/builtin-apply.c index 58d998577e..c6feaf5ca8 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -14,6 +14,7 @@ #include "builtin.h" #include "string-list.h" #include "dir.h" +#include "parse-options.h" /* * --check turns on checking that the working tree matches the @@ -45,9 +46,11 @@ static int apply_verbosely; static int no_add; static const char *fake_ancestor; static int line_termination = '\n'; -static unsigned long p_context = ULONG_MAX; -static const char apply_usage[] = -"git apply [--stat] [--numstat] [--summary] [--check] [--index] [--cached] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [--reverse] [--reject] [--verbose] [-z] [-pNUM] [-CNUM] [--whitespace=<nowarn|warn|fix|error|error-all>] <patch>..."; +static unsigned int p_context = UINT_MAX; +static const char * const apply_usage[] = { + "git apply [options] [<patch>...]", + NULL +}; static enum ws_error_action { nowarn_ws_error, @@ -61,6 +64,8 @@ static int applied_after_fixing_ws; static const char *patch_input_file; static const char *root; static int root_len; +static int read_stdin = 1; +static int options; static void parse_whitespace_option(const char *option) { @@ -2266,6 +2271,25 @@ static struct patch *in_fn_table(const char *name) return NULL; } +/* + * item->util in the filename table records the status of the path. + * Usually it points at a patch (whose result records the contents + * of it after applying it), but it could be PATH_WAS_DELETED for a + * path that a previously applied patch has already removed. + */ + #define PATH_TO_BE_DELETED ((struct patch *) -2) +#define PATH_WAS_DELETED ((struct patch *) -1) + +static int to_be_deleted(struct patch *patch) +{ + return patch == PATH_TO_BE_DELETED; +} + +static int was_deleted(struct patch *patch) +{ + return patch == PATH_WAS_DELETED; +} + static void add_to_fn_table(struct patch *patch) { struct string_list_item *item; @@ -2286,7 +2310,22 @@ static void add_to_fn_table(struct patch *patch) */ if ((patch->new_name == NULL) || (patch->is_rename)) { item = string_list_insert(patch->old_name, &fn_table); - item->util = (struct patch *) -1; + item->util = PATH_WAS_DELETED; + } +} + +static void prepare_fn_table(struct patch *patch) +{ + /* + * store information about incoming file deletion + */ + while (patch) { + if ((patch->new_name == NULL) || (patch->is_rename)) { + struct string_list_item *item; + item = string_list_insert(patch->old_name, &fn_table); + item->util = PATH_TO_BE_DELETED; + } + patch = patch->next; } } @@ -2299,8 +2338,8 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry * struct patch *tpatch; if (!(patch->is_copy || patch->is_rename) && - ((tpatch = in_fn_table(patch->old_name)) != NULL)) { - if (tpatch == (struct patch *) -1) { + (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) { + if (was_deleted(tpatch)) { return error("patch %s has been renamed/deleted", patch->old_name); } @@ -2394,10 +2433,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s assert(patch->is_new <= 0); if (!(patch->is_copy || patch->is_rename) && - (tpatch = in_fn_table(old_name)) != NULL) { - if (tpatch == (struct patch *) -1) { + (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) { + if (was_deleted(tpatch)) return error("%s: has been deleted/renamed", old_name); - } st_mode = tpatch->new_mode; } else if (!cached) { stat_ret = lstat(old_name, st); @@ -2405,6 +2443,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s return error("%s: %s", old_name, strerror(errno)); } + if (to_be_deleted(tpatch)) + tpatch = NULL; + if (check_index && !tpatch) { int pos = cache_name_pos(old_name, strlen(old_name)); if (pos < 0) { @@ -2466,6 +2507,7 @@ static int check_patch(struct patch *patch) const char *new_name = patch->new_name; const char *name = old_name ? old_name : new_name; struct cache_entry *ce = NULL; + struct patch *tpatch; int ok_if_exists; int status; @@ -2476,7 +2518,8 @@ static int check_patch(struct patch *patch) return status; old_name = patch->old_name; - if (in_fn_table(new_name) == (struct patch *) -1) + if ((tpatch = in_fn_table(new_name)) && + (was_deleted(tpatch) || to_be_deleted(tpatch))) /* * A type-change diff is always split into a patch to * delete old, immediately followed by a patch to @@ -2528,6 +2571,7 @@ static int check_patch_list(struct patch *patch) { int err = 0; + prepare_fn_table(patch); while (patch) { if (apply_verbosely) say_patch_name(stderr, @@ -3138,151 +3182,160 @@ static int git_apply_config(const char *var, const char *value, void *cb) return git_default_config(var, value, cb); } +static int option_parse_exclude(const struct option *opt, + const char *arg, int unset) +{ + add_name_limit(arg, 1); + return 0; +} + +static int option_parse_include(const struct option *opt, + const char *arg, int unset) +{ + add_name_limit(arg, 0); + has_include = 1; + return 0; +} + +static int option_parse_p(const struct option *opt, + const char *arg, int unset) +{ + p_value = atoi(arg); + p_value_known = 1; + return 0; +} + +static int option_parse_z(const struct option *opt, + const char *arg, int unset) +{ + if (unset) + line_termination = '\n'; + else + line_termination = 0; + return 0; +} + +static int option_parse_whitespace(const struct option *opt, + const char *arg, int unset) +{ + const char **whitespace_option = opt->value; + + *whitespace_option = arg; + parse_whitespace_option(arg); + return 0; +} + +static int option_parse_directory(const struct option *opt, + const char *arg, int unset) +{ + root_len = strlen(arg); + if (root_len && arg[root_len - 1] != '/') { + char *new_root; + root = new_root = xmalloc(root_len + 2); + strcpy(new_root, arg); + strcpy(new_root + root_len++, "/"); + } else + root = arg; + return 0; +} int cmd_apply(int argc, const char **argv, const char *unused_prefix) { int i; - int read_stdin = 1; - int options = 0; int errs = 0; int is_not_gitdir; + int binary; + int force_apply = 0; const char *whitespace_option = NULL; + struct option builtin_apply_options[] = { + { OPTION_CALLBACK, 0, "exclude", NULL, "path", + "don't apply changes matching the given path", + 0, option_parse_exclude }, + { OPTION_CALLBACK, 0, "include", NULL, "path", + "apply changes matching the given path", + 0, option_parse_include }, + { OPTION_CALLBACK, 'p', NULL, NULL, "num", + "remove <num> leading slashes from traditional diff paths", + 0, option_parse_p }, + OPT_BOOLEAN(0, "no-add", &no_add, + "ignore additions made by the patch"), + OPT_BOOLEAN(0, "stat", &diffstat, + "instead of applying the patch, output diffstat for the input"), + OPT_BOOLEAN(0, "allow-binary-replacement", &binary, + "now no-op"), + OPT_BOOLEAN(0, "binary", &binary, + "now no-op"), + OPT_BOOLEAN(0, "numstat", &numstat, + "shows number of added and deleted lines in decimal notation"), + OPT_BOOLEAN(0, "summary", &summary, + "instead of applying the patch, output a summary for the input"), + OPT_BOOLEAN(0, "check", &check, + "instead of applying the patch, see if the patch is applicable"), + OPT_BOOLEAN(0, "index", &check_index, + "make sure the patch is applicable to the current index"), + OPT_BOOLEAN(0, "cached", &cached, + "apply a patch without touching the working tree"), + OPT_BOOLEAN(0, "apply", &force_apply, + "also apply the patch (use with --stat/--summary/--check)"), + OPT_STRING(0, "build-fake-ancestor", &fake_ancestor, "file", + "build a temporary index based on embedded index information"), + { OPTION_CALLBACK, 'z', NULL, NULL, NULL, + "paths are separated with NUL character", + PARSE_OPT_NOARG, option_parse_z }, + OPT_INTEGER('C', NULL, &p_context, + "ensure at least <n> lines of context match"), + { OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action", + "detect new or modified lines that have whitespace errors", + 0, option_parse_whitespace }, + OPT_BOOLEAN('R', "reverse", &apply_in_reverse, + "apply the patch in reverse"), + OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero, + "don't expect at least one line of context"), + OPT_BOOLEAN(0, "reject", &apply_with_reject, + "leave the rejected hunks in corresponding *.rej files"), + OPT__VERBOSE(&apply_verbosely), + OPT_BIT(0, "inaccurate-eof", &options, + "tolerate incorrectly detected missing new-line at the end of file", + INACCURATE_EOF), + OPT_BIT(0, "recount", &options, + "do not trust the line counts in the hunk headers", + RECOUNT), + { OPTION_CALLBACK, 0, "directory", NULL, "root", + "prepend <root> to all filenames", + 0, option_parse_directory }, + OPT_END() + }; + prefix = setup_git_directory_gently(&is_not_gitdir); prefix_length = prefix ? strlen(prefix) : 0; git_config(git_apply_config, NULL); if (apply_default_whitespace) parse_whitespace_option(apply_default_whitespace); - for (i = 1; i < argc; i++) { + argc = parse_options(argc, argv, builtin_apply_options, + apply_usage, 0); + if (apply_with_reject) + apply = apply_verbosely = 1; + if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) + apply = 0; + if (check_index && is_not_gitdir) + die("--index outside a repository"); + if (cached) { + if (is_not_gitdir) + die("--cached outside a repository"); + check_index = 1; + } + for (i = 0; i < argc; i++) { const char *arg = argv[i]; - char *end; int fd; if (!strcmp(arg, "-")) { errs |= apply_patch(0, "<stdin>", options); read_stdin = 0; continue; - } - if (!prefixcmp(arg, "--exclude=")) { - add_name_limit(arg + 10, 1); - continue; - } - if (!prefixcmp(arg, "--include=")) { - add_name_limit(arg + 10, 0); - has_include = 1; - continue; - } - if (!prefixcmp(arg, "-p")) { - p_value = atoi(arg + 2); - p_value_known = 1; - continue; - } - if (!strcmp(arg, "--no-add")) { - no_add = 1; - continue; - } - if (!strcmp(arg, "--stat")) { - apply = 0; - diffstat = 1; - continue; - } - if (!strcmp(arg, "--allow-binary-replacement") || - !strcmp(arg, "--binary")) { - continue; /* now no-op */ - } - if (!strcmp(arg, "--numstat")) { - apply = 0; - numstat = 1; - continue; - } - if (!strcmp(arg, "--summary")) { - apply = 0; - summary = 1; - continue; - } - if (!strcmp(arg, "--check")) { - apply = 0; - check = 1; - continue; - } - if (!strcmp(arg, "--index")) { - if (is_not_gitdir) - die("--index outside a repository"); - check_index = 1; - continue; - } - if (!strcmp(arg, "--cached")) { - if (is_not_gitdir) - die("--cached outside a repository"); - check_index = 1; - cached = 1; - continue; - } - if (!strcmp(arg, "--apply")) { - apply = 1; - continue; - } - if (!strcmp(arg, "--build-fake-ancestor")) { - apply = 0; - if (++i >= argc) - die ("need a filename"); - fake_ancestor = argv[i]; - continue; - } - if (!strcmp(arg, "-z")) { - line_termination = 0; - continue; - } - if (!prefixcmp(arg, "-C")) { - p_context = strtoul(arg + 2, &end, 0); - if (*end != '\0') - die("unrecognized context count '%s'", arg + 2); - continue; - } - if (!prefixcmp(arg, "--whitespace=")) { - whitespace_option = arg + 13; - parse_whitespace_option(arg + 13); - continue; - } - if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - apply_in_reverse = 1; - continue; - } - if (!strcmp(arg, "--unidiff-zero")) { - unidiff_zero = 1; - continue; - } - if (!strcmp(arg, "--reject")) { - apply = apply_with_reject = apply_verbosely = 1; - continue; - } - if (!strcmp(arg, "-v") || !strcmp(arg, "--verbose")) { - apply_verbosely = 1; - continue; - } - if (!strcmp(arg, "--inaccurate-eof")) { - options |= INACCURATE_EOF; - continue; - } - if (!strcmp(arg, "--recount")) { - options |= RECOUNT; - continue; - } - if (!prefixcmp(arg, "--directory=")) { - arg += strlen("--directory="); - root_len = strlen(arg); - if (root_len && arg[root_len - 1] != '/') { - char *new_root; - root = new_root = xmalloc(root_len + 2); - strcpy(new_root, arg); - strcpy(new_root + root_len++, "/"); - } else - root = arg; - continue; - } - if (0 < prefix_length) + } else if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); fd = open(arg, O_RDONLY); diff --git a/builtin-blame.c b/builtin-blame.c index e462a6ec50..1ead9b4830 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1264,11 +1264,12 @@ struct commit_info * Parse author/committer line in the commit object buffer */ static void get_ac_line(const char *inbuf, const char *what, - int bufsz, char *person, const char **mail, + int person_len, char *person, + int mail_len, char *mail, unsigned long *time, const char **tz) { int len, tzlen, maillen; - char *tmp, *endp, *timepos; + char *tmp, *endp, *timepos, *mailpos; tmp = strstr(inbuf, what); if (!tmp) @@ -1279,10 +1280,11 @@ static void get_ac_line(const char *inbuf, const char *what, len = strlen(tmp); else len = endp - tmp; - if (bufsz <= len) { + if (person_len <= len) { error_out: /* Ugh */ - *mail = *tz = "(unknown)"; + *tz = "(unknown)"; + strcpy(mail, *tz); *time = 0; return; } @@ -1305,9 +1307,10 @@ static void get_ac_line(const char *inbuf, const char *what, *tmp = 0; while (*tmp != ' ') tmp--; - *mail = tmp + 1; + mailpos = tmp + 1; *tmp = 0; maillen = timepos - tmp; + memcpy(mail, mailpos, maillen); if (!mailmap.nr) return; @@ -1316,20 +1319,23 @@ static void get_ac_line(const char *inbuf, const char *what, * mailmap expansion may make the name longer. * make room by pushing stuff down. */ - tmp = person + bufsz - (tzlen + 1); + tmp = person + person_len - (tzlen + 1); memmove(tmp, *tz, tzlen); tmp[tzlen] = 0; *tz = tmp; - tmp = tmp - (maillen + 1); - memmove(tmp, *mail, maillen); - tmp[maillen] = 0; - *mail = tmp; - /* - * Now, convert e-mail using mailmap + * Now, convert both name and e-mail using mailmap */ - map_email(&mailmap, tmp + 1, person, tmp-person-1); + if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) { + /* Add a trailing '>' to email, since map_user returns plain emails + Note: It already has '<', since we replace from mail+1 */ + mailpos = memchr(mail, '\0', mail_len); + if (mailpos && mailpos-mail < mail_len - 1) { + *mailpos = '>'; + *(mailpos+1) = '\0'; + } + } } static void get_commit_info(struct commit *commit, @@ -1338,8 +1344,10 @@ static void get_commit_info(struct commit *commit, { int len; char *tmp, *endp, *reencoded, *message; - static char author_buf[1024]; - static char committer_buf[1024]; + static char author_name[1024]; + static char author_mail[1024]; + static char committer_name[1024]; + static char committer_mail[1024]; static char summary_buf[1024]; /* @@ -1357,9 +1365,11 @@ static void get_commit_info(struct commit *commit, } reencoded = reencode_commit_message(commit, NULL); message = reencoded ? reencoded : commit->buffer; - ret->author = author_buf; + ret->author = author_name; + ret->author_mail = author_mail; get_ac_line(message, "\nauthor ", - sizeof(author_buf), author_buf, &ret->author_mail, + sizeof(author_name), author_name, + sizeof(author_mail), author_mail, &ret->author_time, &ret->author_tz); if (!detailed) { @@ -1367,9 +1377,11 @@ static void get_commit_info(struct commit *commit, return; } - ret->committer = committer_buf; + ret->committer = committer_name; + ret->committer_mail = committer_mail; get_ac_line(message, "\ncommitter ", - sizeof(committer_buf), committer_buf, &ret->committer_mail, + sizeof(committer_name), committer_name, + sizeof(committer_mail), committer_mail, &ret->committer_time, &ret->committer_tz); ret->summary = summary_buf; @@ -2396,7 +2408,7 @@ parse_done: sb.ent = ent; sb.path = path; - read_mailmap(&mailmap, ".mailmap", NULL); + read_mailmap(&mailmap, NULL); if (!incremental) setup_pager(); diff --git a/builtin-branch.c b/builtin-branch.c index 23b6949fec..07166b8856 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -99,6 +99,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) const char *fmt, *remote; int i; int ret = 0; + struct strbuf bname = STRBUF_INIT; switch (kinds) { case REF_REMOTE_BRANCH: @@ -119,20 +120,25 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!head_rev) die("Couldn't look up commit object for HEAD"); } - for (i = 0; i < argc; i++) { - if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) { + for (i = 0; i < argc; i++, strbuf_release(&bname)) { + int len = strlen(argv[i]); + + if (interpret_nth_last_branch(argv[i], &bname) != len) + strbuf_add(&bname, argv[i], len); + + if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { error("Cannot delete the branch '%s' " - "which you are currently on.", argv[i]); + "which you are currently on.", bname.buf); ret = 1; continue; } free(name); - name = xstrdup(mkpath(fmt, argv[i])); + name = xstrdup(mkpath(fmt, bname.buf)); if (!resolve_ref(name, sha1, 1, NULL)) { error("%sbranch '%s' not found.", - remote, argv[i]); + remote, bname.buf); ret = 1; continue; } @@ -152,22 +158,23 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) if (!force && !in_merge_bases(rev, &head_rev, 1)) { error("The branch '%s' is not an ancestor of " - "your current HEAD.\n" - "If you are sure you want to delete it, " - "run 'git branch -D %s'.", argv[i], argv[i]); + "your current HEAD.\n" + "If you are sure you want to delete it, " + "run 'git branch -D %s'.", bname.buf, bname.buf); ret = 1; continue; } if (delete_ref(name, sha1, 0)) { error("Error deleting %sbranch '%s'", remote, - argv[i]); + bname.buf); ret = 1; } else { struct strbuf buf = STRBUF_INIT; - printf("Deleted %sbranch %s (was %s).\n", remote, argv[i], - find_unique_abbrev(sha1, DEFAULT_ABBREV)); - strbuf_addf(&buf, "branch.%s", argv[i]); + printf("Deleted %sbranch %s (was %s).\n", remote, + bname.buf, + find_unique_abbrev(sha1, DEFAULT_ABBREV)); + strbuf_addf(&buf, "branch.%s", bname.buf); if (git_config_rename_section(buf.buf, NULL) < 0) warning("Update of config-file failed"); strbuf_release(&buf); @@ -193,21 +200,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 +223,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 +393,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 +459,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 +494,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-cat-file.c b/builtin-cat-file.c index 30d00a6664..8fad19daed 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -137,7 +137,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name) break; default: - die("git cat-file: unknown option: %s\n", exp_type); + die("git cat-file: unknown option: %s", exp_type); } if (!buf) diff --git a/builtin-checkout.c b/builtin-checkout.c index b5dd9c07b4..ffdb33aef5 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -5,6 +5,7 @@ #include "commit.h" #include "tree.h" #include "tree-walk.h" +#include "cache-tree.h" #include "unpack-trees.h" #include "dir.h" #include "run-command.h" @@ -38,23 +39,13 @@ struct checkout_opts { static int post_checkout_hook(struct commit *old, struct commit *new, int changed) { - struct child_process proc; - const char *name = git_path("hooks/post-checkout"); - const char *argv[5]; + return run_hook(NULL, "post-checkout", + sha1_to_hex(old ? old->object.sha1 : null_sha1), + sha1_to_hex(new ? new->object.sha1 : null_sha1), + changed ? "1" : "0", NULL); + /* "new" can be NULL when checking out from the index before + a commit exists. */ - if (access(name, X_OK) < 0) - return 0; - - memset(&proc, 0, sizeof(proc)); - argv[0] = name; - argv[1] = xstrdup(sha1_to_hex(old ? old->object.sha1 : null_sha1)); - argv[2] = xstrdup(sha1_to_hex(new->object.sha1)); - argv[3] = changed ? "1" : "0"; - argv[4] = NULL; - proc.argv = argv; - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - return run_command(&proc); } static int update_some(const unsigned char *sha1, const char *base, int baselen, @@ -63,9 +54,6 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen, int len; struct cache_entry *ce; - if (S_ISGITLINK(mode)) - return 0; - if (S_ISDIR(mode)) return READ_TREE_RECURSIVE; @@ -240,7 +228,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)) @@ -249,7 +237,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) { @@ -274,7 +262,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; @@ -361,8 +349,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); } @@ -503,10 +499,10 @@ static void update_refs_for_switch(struct checkout_opts *opts, create_symref("HEAD", new->path, msg.buf); if (!opts->quiet) { if (old->path && !strcmp(new->path, old->path)) - fprintf(stderr, "Already on \"%s\"\n", + fprintf(stderr, "Already on '%s'\n", new->name); else - fprintf(stderr, "Switched to%s branch \"%s\"\n", + fprintf(stderr, "Switched to%s branch '%s'\n", opts->new_branch ? " a new" : "", new->name); } @@ -515,7 +511,7 @@ static void update_refs_for_switch(struct checkout_opts *opts, REF_NODEREF, DIE_ON_ERR); if (!opts->quiet) { if (old->path) - fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b <new_branch_name>\n", new->name); + fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b <new_branch_name>\n", new->name); describe_detached_head("HEAD is now at", new->commit); } } @@ -671,6 +667,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 2feac9c5cb..c338910b1c 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: @@ -192,15 +193,15 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) dir = opendir(src->buf); if (!dir) - die("failed to open %s\n", src->buf); + die("failed to open %s", src->buf); if (mkdir(dest->buf, 0777)) { if (errno != EEXIST) - die("failed to create directory %s\n", dest->buf); + die("failed to create directory %s", dest->buf); else if (stat(dest->buf, &buf)) - die("failed to stat %s\n", dest->buf); + die("failed to stat %s", dest->buf); else if (!S_ISDIR(buf.st_mode)) - die("%s exists and is not a directory\n", dest->buf); + die("%s exists and is not a directory", dest->buf); } strbuf_addch(src, '/'); @@ -224,16 +225,16 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) } if (unlink(dest->buf) && errno != ENOENT) - die("failed to unlink %s\n", dest->buf); + die("failed to unlink %s", dest->buf); if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; if (option_local) - die("failed to create link %s\n", dest->buf); + die("failed to create link %s", dest->buf); option_no_hardlinks = 1; } if (copy_file(dest->buf, src->buf, 0666)) - die("failed to copy file to %s\n", dest->buf); + die("failed to copy file to %s", dest->buf); } closedir(dir); } @@ -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); } @@ -349,6 +350,19 @@ static struct ref *write_remote_refs(const struct ref *refs, return local_refs; } +static void install_branch_config(const char *local, + const char *origin, + const char *remote) +{ + struct strbuf key = STRBUF_INIT; + strbuf_addf(&key, "branch.%s.remote", local); + git_config_set(key.buf, origin); + strbuf_reset(&key); + strbuf_addf(&key, "branch.%s.merge", local); + git_config_set(key.buf, remote); + strbuf_release(&key); +} + int cmd_clone(int argc, const char **argv, const char *prefix) { int use_local_hardlinks = 1; @@ -357,6 +371,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct stat buf; const char *repo_name, *repo, *work_tree, *git_dir; char *path, *dir; + int dest_exists; const struct ref *refs, *head_points_at, *remote_head, *mapped_refs; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; @@ -406,8 +421,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dir = guess_dir_name(repo_name, is_bundle, option_bare); strip_trailing_slashes(dir); - if (!stat(dir, &buf)) - die("destination directory '%s' already exists.", dir); + dest_exists = !stat(dir, &buf); + if (dest_exists && !is_empty_dir(dir)) + die("destination path '%s' already exists and is not " + "an empty directory.", dir); strbuf_addf(&reflog_msg, "clone: from %s", repo); @@ -431,14 +448,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (safe_create_leading_directories_const(work_tree) < 0) die("could not create leading directories of '%s': %s", work_tree, strerror(errno)); - if (mkdir(work_tree, 0755)) + if (!dest_exists && mkdir(work_tree, 0755)) die("could not create work tree dir '%s': %s.", work_tree, strerror(errno)); set_git_work_tree(work_tree); } 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); @@ -519,14 +536,26 @@ 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 (!option_bare) + install_branch_config("master", option_origin, + "refs/heads/master"); + } if (head_points_at) { /* Local default branch link */ @@ -554,11 +583,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) head_points_at->peer_ref->name, reflog_msg.buf); - strbuf_addf(&key, "branch.%s.remote", head); - git_config_set(key.buf, option_origin); - strbuf_reset(&key); - strbuf_addf(&key, "branch.%s.merge", head); - git_config_set(key.buf, head_points_at->name); + install_branch_config(head, option_origin, + head_points_at->name); } } else if (remote_head) { /* Source had detached HEAD pointing somewhere. */ diff --git a/builtin-commit.c b/builtin-commit.c index 72dd0b9553..81371b1d26 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); } @@ -362,40 +362,6 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int return s.commitable; } -static int run_hook(const char *index_file, const char *name, ...) -{ - struct child_process hook; - const char *argv[10], *env[2]; - char index[PATH_MAX]; - va_list args; - int i; - - va_start(args, name); - argv[0] = git_path("hooks/%s", name); - i = 0; - do { - if (++i >= ARRAY_SIZE(argv)) - die ("run_hook(): too many arguments"); - argv[i] = va_arg(args, const char *); - } while (argv[i]); - va_end(args); - - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); - env[0] = index; - env[1] = NULL; - - if (access(argv[0], X_OK) < 0) - return 0; - - memset(&hook, 0, sizeof(hook)); - hook.argv = argv; - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - hook.env = env; - - return run_command(&hook); -} - static int is_a_merge(const unsigned char *sha1) { struct commit *commit = lookup_commit(sha1); @@ -596,7 +562,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix) commitable = run_status(fp, index_file, prefix, 1); wt_status_use_color = saved_color_setting; } else { - struct rev_info rev; unsigned char sha1[20]; const char *parent = "HEAD"; @@ -608,16 +573,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix) if (get_sha1(parent, sha1)) commitable = !!active_nr; - else { - init_revisions(&rev, ""); - rev.abbrev = 0; - setup_revisions(0, NULL, &rev, parent); - DIFF_OPT_SET(&rev.diffopt, QUIET); - DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); - run_diff_index(&rev, 1 /* cached */); - - commitable = !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); - } + else + commitable = index_differs_from(parent, 0); } fclose(fp); @@ -884,7 +841,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) { struct rev_info rev; struct commit *commit; - static const char *format = "format:%h: \"%s\""; + static const char *format = "format:%h] %s"; unsigned char junk_sha1[20]; const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL); @@ -911,7 +868,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1) rev.diffopt.break_opt = 0; diff_setup_done(&rev.diffopt); - printf("[%s%s]: created ", + printf("[%s%s ", !prefixcmp(head, "refs/heads/") ? head + 11 : !strcmp(head, "HEAD") ? diff --git a/builtin-count-objects.c b/builtin-count-objects.c index 38b033fd71..b814fe5070 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -5,6 +5,7 @@ */ #include "cache.h" +#include "dir.h" #include "builtin.h" #include "parse-options.h" @@ -21,9 +22,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose, const char *cp; int bad = 0; - if ((ent->d_name[0] == '.') && - (ent->d_name[1] == 0 || - ((ent->d_name[1] == '.') && (ent->d_name[2] == 0)))) + if (is_dot_or_dotdot(ent->d_name)) continue; for (cp = ent->d_name; *cp; cp++) { int ch = *cp; diff --git a/builtin-fast-export.c b/builtin-fast-export.c index fdf4ae9ebd..f171ee4a2b 100644 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -362,7 +362,10 @@ static void get_tags_and_duplicates(struct object_array *pending, break; case OBJ_TAG: tag = (struct tag *)e->item; + + /* handle nested tags */ while (tag && tag->object.type == OBJ_TAG) { + parse_object(tag->object.sha1); string_list_append(full_name, extra_refs)->util = tag; tag = (struct tag *)tag->tagged; } @@ -375,11 +378,17 @@ static void get_tags_and_duplicates(struct object_array *pending, case OBJ_BLOB: handle_object(tag->object.sha1); continue; + default: /* OBJ_TAG (nested tags) is already handled */ + warning("Tag points to object of unexpected type %s, skipping.", + typename(tag->object.type)); + continue; } break; default: - die ("Unexpected object of type %s", - typename(e->item->type)); + warning("%s: Unexpected object of type %s, skipping.", + e->name, + typename(e->item->type)); + continue; } if (commit->util) /* more than one name for the same object */ diff --git a/builtin-fetch--tool.c b/builtin-fetch--tool.c index 469b07e240..29356d25db 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-pack.c b/builtin-fetch-pack.c index 67fb80ec48..1effdc0d6b 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -13,6 +13,7 @@ static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; static int unpack_limit = 100; +static int prefer_ofs_delta = 1; static struct fetch_pack_args args = { /* .uploadpack = */ "git-upload-pack", }; @@ -200,7 +201,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, (args.use_thin_pack ? " thin-pack" : ""), (args.no_progress ? " no-progress" : ""), (args.include_tag ? " include-tag" : ""), - " ofs-delta"); + (prefer_ofs_delta ? " ofs-delta" : "")); else packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); fetching++; @@ -597,6 +598,11 @@ static struct ref *do_fetch_pack(int fd[2], fprintf(stderr, "Server supports side-band\n"); use_sideband = 1; } + if (server_supports("ofs-delta")) { + if (args.verbose) + fprintf(stderr, "Server supports ofs-delta\n"); + } else + prefer_ofs_delta = 0; if (everything_local(&ref, nr_match, match)) { packet_flush(fd[1]); goto all_done; @@ -649,6 +655,11 @@ static int fetch_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "repack.usedeltabaseoffset") == 0) { + prefer_ofs_delta = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } diff --git a/builtin-fetch.c b/builtin-fetch.c index 7568163af2..7fb35fca9d 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); } @@ -607,7 +608,7 @@ static void set_option(const char *name, const char *value) { int r = transport_set_option(transport, name, value); if (r < 0) - die("Option \"%s\" value \"%s\" is not valid for %s\n", + die("Option \"%s\" value \"%s\" is not valid for %s", name, value, transport->url); if (r > 0) warning("Option \"%s\" is ignored for %s\n", @@ -635,6 +636,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) else remote = remote_get(argv[0]); + if (!remote) + die("Where do you want to fetch from today?"); + transport = transport_get(remote, remote->url[0]); if (verbosity >= 2) transport->verbose = 1; @@ -647,9 +651,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth) set_option(TRANS_OPT_DEPTH, depth); - if (!transport->url) - die("Where do you want to fetch from today?"); - if (argc > 1) { int j = 0; refs = xcalloc(argc + 1, sizeof(const char *)); @@ -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 a60d199526..6436bc2248 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -10,6 +10,7 @@ #include "tree-walk.h" #include "fsck.h" #include "parse-options.h" +#include "dir.h" #define REACHABLE 0x0001 #define SEEN 0x0002 @@ -22,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; @@ -395,19 +397,12 @@ static void fsck_dir(int i, char *path) while ((de = readdir(dir)) != NULL) { char name[100]; unsigned char sha1[20]; - int len = strlen(de->d_name); - switch (len) { - case 2: - if (de->d_name[1] != '.') - break; - case 1: - if (de->d_name[0] != '.') - break; + if (is_dot_or_dotdot(de->d_name)) continue; - case 38: + if (strlen(de->d_name) == 38) { sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, len+1); + memcpy(name+2, de->d_name, 39); if (get_sha1_hex(name, sha1) < 0) break; add_sha1_list(sha1, DIRENT_SORT_HINT(de)); @@ -479,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); @@ -518,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")) @@ -534,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", @@ -590,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; @@ -601,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 */ @@ -630,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-gc.c b/builtin-gc.c index f8eae4adb4..8d990ed493 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -144,34 +144,6 @@ static int too_many_packs(void) return gc_auto_pack_limit <= cnt; } -static int run_hook(void) -{ - const char *argv[2]; - struct child_process hook; - int ret; - - argv[0] = git_path("hooks/pre-auto-gc"); - argv[1] = NULL; - - if (access(argv[0], X_OK) < 0) - return 0; - - memset(&hook, 0, sizeof(hook)); - hook.argv = argv; - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - - ret = start_command(&hook); - if (ret) { - warning("Could not spawn %s", argv[0]); - return ret; - } - ret = finish_command(&hook); - if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL) - warning("%s exited due to uncaught signal", argv[0]); - return ret; -} - static int need_to_gc(void) { /* @@ -189,26 +161,28 @@ static int need_to_gc(void) */ if (too_many_packs()) append_option(argv_repack, - !strcmp(prune_expire, "now") ? "-a" : "-A", + prune_expire && !strcmp(prune_expire, "now") ? + "-a" : "-A", MAX_ADD); else if (!too_many_loose_objects()) return 0; - if (run_hook()) + if (run_hook(NULL, "pre-auto-gc", NULL)) return 0; return 1; } int cmd_gc(int argc, const char **argv, const char *prefix) { - int prune = 0; int aggressive = 0; int auto_gc = 0; int quiet = 0; char buf[80]; struct option builtin_gc_options[] = { - OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects (deprecated)"), + { OPTION_STRING, 0, "prune", &prune_expire, "date", + "prune unreferenced objects", + PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"), OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"), OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"), @@ -246,7 +220,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) "\"git help gc\" for more information.\n"); } else append_option(argv_repack, - !strcmp(prune_expire, "now") ? "-a" : "-A", + prune_expire && !strcmp(prune_expire, "now") + ? "-a" : "-A", MAX_ADD); if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD)) @@ -258,9 +233,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (run_command_v_opt(argv_repack, RUN_GIT_CMD)) return error(FAILED_RUN, argv_repack[0]); - argv_prune[2] = prune_expire; - if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) - return error(FAILED_RUN, argv_prune[0]); + if (prune_expire) { + argv_prune[2] = prune_expire; + if (run_command_v_opt(argv_prune, RUN_GIT_CMD)) + return error(FAILED_RUN, argv_prune[0]); + } if (run_command_v_opt(argv_rerere, RUN_GIT_CMD)) return error(FAILED_RUN, argv_rerere[0]); diff --git a/builtin-help.c b/builtin-help.c index f076efa921..9b57a74618 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-init-db.c b/builtin-init-db.c index 6cc945d507..bfdfc7b411 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -29,7 +29,7 @@ static void safe_create_dir(const char *dir, int share) } } else if (share && adjust_shared_perm(dir)) - die("Could not make %s writable by group\n", dir); + die("Could not make %s writable by group", dir); } static void copy_templates_1(char *path, int baselen, @@ -197,6 +197,8 @@ static int create_default_files(const char *template_path) git_config(git_default_config, NULL); is_bare_repository_cfg = init_is_bare_repository; + + /* reading existing config may have overwrote it */ if (init_shared_repository != -1) shared_repository = init_shared_repository; @@ -315,12 +317,15 @@ int init_db(const char *template_dir, unsigned int flags) * and compatibility values for PERM_GROUP and * PERM_EVERYBODY. */ - if (shared_repository == PERM_GROUP) + if (shared_repository < 0) + /* force to the mode value */ + sprintf(buf, "0%o", -shared_repository); + else if (shared_repository == PERM_GROUP) sprintf(buf, "%d", OLD_PERM_GROUP); else if (shared_repository == PERM_EVERYBODY) sprintf(buf, "%d", OLD_PERM_EVERYBODY); else - sprintf(buf, "0%o", shared_repository); + die("oops"); git_config_set("core.sharedrepository", buf); git_config_set("receive.denyNonFastforwards", "true"); } @@ -400,6 +405,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) usage(init_db_usage); } + if (init_shared_repository != -1) + shared_repository = init_shared_repository; + /* * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR * without --bare. Catch the error early. diff --git a/builtin-log.c b/builtin-log.c index 60f8dd8604..0f0adf2bab 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -16,6 +16,7 @@ #include "patch-ids.h" #include "run-command.h" #include "shortlog.h" +#include "remote.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -249,22 +250,13 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) static void show_tagger(char *buf, int len, struct rev_info *rev) { - char *email_end, *p; - unsigned long date; - int tz; + struct strbuf out = STRBUF_INIT; - email_end = memchr(buf, '>', len); - if (!email_end) - return; - p = ++email_end; - while (isspace(*p)) - p++; - date = strtoul(p, &p, 10); - while (isspace(*p)) - p++; - tz = (int)strtol(p, NULL, 10); - printf("Tagger: %.*s\nDate: %s\n", (int)(email_end - buf), buf, - show_date(date, tz, rev->date_mode)); + pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode, + git_log_output_encoding ? + git_log_output_encoding: git_commit_encoding); + printf("%s\n", out.buf); + strbuf_release(&out); } static int show_object(const unsigned char *sha1, int show_tag_object, @@ -846,7 +838,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); endpos = strchr(committer, '>'); if (!endpos) - die("bogus committer info %s\n", committer); + die("bogus committer info %s", committer); add_signoff = xmemdupz(committer, endpos - committer + 1); } else if (!strcmp(argv[i], "--attach")) { @@ -925,8 +917,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) die ("-n and -k are mutually exclusive."); if (keep_subject && subject_prefix) die ("--subject-prefix and -k are mutually exclusive."); - if (numbered_files && use_stdout) - die ("--numbered-files and --stdout are mutually exclusive."); argc = setup_revisions(argc, argv, &rev, "HEAD"); if (argc > 1) @@ -1099,13 +1089,14 @@ static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) } static const char cherry_usage[] = -"git cherry [-v] <upstream> [<head>] [<limit>]"; +"git cherry [-v] [<upstream> [<head> [<limit>]]]"; int cmd_cherry(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct patch_ids ids; struct commit *commit; struct commit_list *list = NULL; + struct branch *current_branch; const char *upstream; const char *head = "HEAD"; const char *limit = NULL; @@ -1128,7 +1119,17 @@ int cmd_cherry(int argc, const char **argv, const char *prefix) upstream = argv[1]; break; default: - usage(cherry_usage); + current_branch = branch_get(NULL); + if (!current_branch || !current_branch->merge + || !current_branch->merge[0] + || !current_branch->merge[0]->dst) { + fprintf(stderr, "Could not find a tracked" + " remote branch, please" + " specify <upstream> manually.\n"); + usage(cherry_usage); + } + + upstream = current_branch->merge[0]->dst; } init_revisions(&revs, prefix); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index f72eb85475..ca6f33d046 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 && @@ -298,6 +262,21 @@ static const char *verify_pathspec(const char *prefix) return max ? xmemdupz(prev, max) : NULL; } +static void strip_trailing_slash_from_submodules(void) +{ + const char **p; + + for (p = pathspec; *p != NULL; p++) { + int len = strlen(*p), pos; + + if (len < 1 || (*p)[len - 1] != '/') + continue; + pos = cache_name_pos(*p, len - 1); + if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode)) + *p = xstrndup(*p, len - 1); + } +} + /* * Read the tree specified with --with-tree option * (typically, HEAD) into stage #1 and then @@ -440,6 +419,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) { show_deleted = 1; + require_work_tree = 1; continue; } if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { @@ -546,6 +526,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) pathspec = get_pathspec(prefix, argv + i); + /* be nice with submodule patsh ending in a slash */ + read_cache(); + if (pathspec) + strip_trailing_slash_from_submodules(); + /* Verify that the pathspec matches the prefix */ if (pathspec) prefix = verify_pathspec(prefix); @@ -569,7 +554,6 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) show_killed | show_modified)) show_cached = 1; - read_cache(); if (prefix) prune_cache(prefix); if (with_tree) { diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 5b63e6eada..fca46312f6 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -68,13 +68,8 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, * * Something similar to this incomplete example: * - if (show_subprojects(base, baselen, pathname)) { - struct child_process ls_tree; - - ls_tree.dir = base; - ls_tree.argv = ls-tree; - start_command(&ls_tree); - } + if (show_subprojects(base, baselen, pathname)) + retval = READ_TREE_RECURSIVE; * */ type = commit_type; diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index dacc8ac2d0..2789ccdf7d 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); @@ -487,7 +498,7 @@ static void convert_to_utf8(struct strbuf *line, const char *charset) return; out = reencode_string(line->buf, metainfo_charset, charset); if (!out) - die("cannot convert from %s to %s\n", + die("cannot convert from %s to %s", charset, metainfo_charset); strbuf_attach(line, out, strlen(out), strlen(out)); } diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 6b534c1a66..703045bfc8 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -33,7 +33,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) } if (argc < 4) - die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]); + die("Usage: %s <base>... -- <head> <remote> ...", argv[0]); for (i = 1; i < argc; ++i) { if (!strcmp(argv[i], "--")) diff --git a/builtin-merge.c b/builtin-merge.c index 2179e0686d..c339380cf3 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 }; @@ -300,35 +300,6 @@ static void squash_message(void) strbuf_release(&out); } -static int run_hook(const char *name) -{ - struct child_process hook; - const char *argv[3], *env[2]; - char index[PATH_MAX]; - - argv[0] = git_path("hooks/%s", name); - if (access(argv[0], X_OK) < 0) - return 0; - - snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", get_index_file()); - env[0] = index; - env[1] = NULL; - - if (squash) - argv[1] = "1"; - else - argv[1] = "0"; - argv[2] = NULL; - - memset(&hook, 0, sizeof(hook)); - hook.argv = argv; - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - hook.env = env; - - return run_command(&hook); -} - static void finish(const unsigned char *new_head, const char *msg) { struct strbuf reflog_message = STRBUF_INIT; @@ -374,7 +345,7 @@ static void finish(const unsigned char *new_head, const char *msg) } /* Run a post-merge hook */ - run_hook("post-merge"); + run_hook(NULL, "post-merge", squash ? "1" : "0", NULL); strbuf_release(&reflog_message); } @@ -385,9 +356,14 @@ static void merge_name(const char *remote, struct strbuf *msg) struct object *remote_head; unsigned char branch_head[20], buf_sha[20]; struct strbuf buf = STRBUF_INIT; + struct strbuf bname = STRBUF_INIT; const char *ptr; int len, early; + len = strlen(remote); + if (interpret_nth_last_branch(remote, &bname) == len) + remote = bname.buf; + memset(branch_head, 0, sizeof(branch_head)); remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); if (!remote_head) @@ -400,7 +376,7 @@ static void merge_name(const char *remote, struct strbuf *msg) if (!hashcmp(remote_head->sha1, branch_head)) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", sha1_to_hex(branch_head), remote); - return; + goto cleanup; } /* See if remote matches <name>^^^.. or <name>~<number> */ @@ -440,7 +416,8 @@ static void merge_name(const char *remote, struct strbuf *msg) sha1_to_hex(remote_head->sha1), truname.buf + 11, (early ? " (early part)" : "")); - return; + strbuf_release(&truname); + goto cleanup; } } @@ -461,10 +438,13 @@ static void merge_name(const char *remote, struct strbuf *msg) strbuf_remove(&line, ptr-line.buf+1, 13); strbuf_addbuf(msg, &line); strbuf_release(&line); - return; + goto cleanup; } strbuf_addf(msg, "%s\t\tcommit '%s'\n", sha1_to_hex(remote_head->sha1), remote); +cleanup: + strbuf_release(&buf); + strbuf_release(&bname); } static int git_merge_config(const char *k, const char *v, void *cb) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index d1553455ba..b859cb147c 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -78,7 +78,7 @@ static int progress = 1; static int window = 10; static uint32_t pack_size_limit, pack_size_limit_cfg; static int depth = 50; -static int delta_search_threads = 1; +static int delta_search_threads; static int pack_to_stdout; static int num_preferred_base; static struct progress *progress_state; @@ -1611,11 +1611,18 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, find_deltas(list, &list_size, window, depth, processed); return; } + if (progress > pack_to_stdout) + fprintf(stderr, "Delta compression using %d threads.\n", + delta_search_threads); /* Partition the work amongst work threads. */ for (i = 0; i < delta_search_threads; i++) { unsigned sub_size = list_size / (delta_search_threads - i); + /* don't use too small segments or no deltas will be found */ + if (sub_size < 2*window && i+1 < delta_search_threads) + sub_size = 0; + p[i].window = window; p[i].depth = depth; p[i].processed = processed; @@ -1900,13 +1907,19 @@ static void show_commit(struct commit *commit) commit->object.flags |= OBJECT_ADDED; } -static void show_object(struct object_array_entry *p) +static void show_object(struct object *obj, const struct name_path *path, const char *last) { - add_preferred_base_object(p->name); - add_object_entry(p->item->sha1, p->item->type, p->name, 0); - p->item->flags |= OBJECT_ADDED; - free((char *)p->name); - p->name = NULL; + char *name = path_name(path, last); + + add_preferred_base_object(name); + add_object_entry(obj->sha1, obj->type, name, 0); + obj->flags |= OBJECT_ADDED; + + /* + * We will have generated the hash from the name, + * but not saved a pointer to it - we can free it + */ + free((char *)name); } static void show_edge(struct commit *commit) diff --git a/builtin-prune.c b/builtin-prune.c index 7b4ec80e62..545e9c1f94 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -5,6 +5,7 @@ #include "builtin.h" #include "reachable.h" #include "parse-options.h" +#include "dir.h" static const char * const prune_usage[] = { "git prune [-n] [-v] [--expire <time>] [--] [<head>...]", @@ -61,19 +62,12 @@ static int prune_dir(int i, char *path) while ((de = readdir(dir)) != NULL) { char name[100]; unsigned char sha1[20]; - int len = strlen(de->d_name); - switch (len) { - case 2: - if (de->d_name[1] != '.') - break; - case 1: - if (de->d_name[0] != '.') - break; + if (is_dot_or_dotdot(de->d_name)) continue; - case 38: + if (strlen(de->d_name) == 38) { sprintf(name, "%02x", i); - memcpy(name+2, de->d_name, len+1); + memcpy(name+2, de->d_name, 39); if (get_sha1_hex(name, sha1) < 0) break; diff --git a/builtin-push.c b/builtin-push.c index 122fdcfbdc..ca36fb1e58 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -53,8 +53,11 @@ static int do_push(const char *repo, int flags) int i, errs; struct remote *remote = remote_get(repo); - if (!remote) - die("bad repository '%s'", repo); + if (!remote) { + if (repo) + die("bad repository '%s'", repo); + die("No destination configured to push to."); + } if (remote->mirror) flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 38fef34d3f..391d709704 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -29,41 +29,6 @@ static int list_tree(unsigned char *sha1) return 0; } -static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) -{ - struct tree_desc desc; - struct name_entry entry; - int cnt; - - hashcpy(it->sha1, tree->object.sha1); - init_tree_desc(&desc, tree->buffer, tree->size); - cnt = 0; - while (tree_entry(&desc, &entry)) { - if (!S_ISDIR(entry.mode)) - cnt++; - else { - struct cache_tree_sub *sub; - struct tree *subtree = lookup_tree(entry.sha1); - if (!subtree->object.parsed) - parse_tree(subtree); - sub = cache_tree_sub(it, entry.path); - sub->cache_tree = cache_tree(); - prime_cache_tree_rec(sub->cache_tree, subtree); - cnt += sub->cache_tree->entry_count; - } - } - it->entry_count = cnt; -} - -static void prime_cache_tree(void) -{ - if (!nr_trees) - return; - active_cache_tree = cache_tree(); - prime_cache_tree_rec(active_cache_tree, trees[0]); - -} - static const char read_tree_usage[] = "git read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])"; static struct lock_file lock_file; @@ -211,7 +176,6 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) case 3: default: opts.fn = threeway_merge; - cache_tree_free(&active_cache_tree); break; } @@ -221,6 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) opts.head_idx = 1; } + cache_tree_free(&active_cache_tree); for (i = 0; i < nr_trees; i++) { struct tree *tree = trees[i]; parse_tree(tree); @@ -234,11 +199,14 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * "-m ent" or "--reset ent" form), we can obtain a fully * valid cache-tree because the index must match exactly * what came from the tree. + * + * The same holds true if we are switching between two trees + * using read-tree -m A B. The index must match B after that. */ - if (nr_trees && !opts.prefix && (!opts.merge || (stage == 2))) { - cache_tree_free(&active_cache_tree); - prime_cache_tree(); - } + if (nr_trees == 1 && !opts.prefix) + prime_cache_tree(&active_cache_tree, trees[0]); + else if (nr_trees == 2 && opts.merge) + prime_cache_tree(&active_cache_tree, trees[1]); if (write_cache(newfd, active_cache, active_nr) || commit_locked_index(&lock_file)) diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index db67c3162c..849f1fe6f9 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -9,22 +9,25 @@ #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_UNCONFIGURED, DENY_IGNORE, DENY_WARN, DENY_REFUSE, }; -static int deny_deletes = 0; -static int deny_non_fast_forwards = 0; -static enum deny_action deny_current_branch = DENY_WARN; +static int deny_deletes; +static int deny_non_fast_forwards; +static enum deny_action deny_current_branch = DENY_UNCONFIGURED; +static enum deny_action deny_delete_current = DENY_UNCONFIGURED; static int receive_fsck_objects; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; +static const char *head_name; static char capabilities[] = " report-status delete-refs "; static int capabilities_sent; @@ -76,6 +79,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.denydeletecurrent") == 0) { + deny_delete_current = parse_deny_action(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -136,7 +144,7 @@ static int hook_status(int code, const char *hook_name) } } -static int run_hook(const char *hook_name) +static int run_receive_hook(const char *hook_name) { static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4]; struct command *cmd; @@ -202,16 +210,67 @@ static int run_update_hook(struct command *cmd) static int is_ref_checked_out(const char *ref) { - unsigned char sha1[20]; - const char *head; - if (is_bare_repository()) return 0; - head = resolve_ref("HEAD", sha1, 0, NULL); - if (!head) + if (!head_name) return 0; - return !strcmp(head, ref); + return !strcmp(head_name, ref); +} + +static char *warn_unconfigured_deny_msg[] = { + "Updating the currently checked out branch may cause confusion,", + "as the index and work tree do not reflect changes that are in HEAD.", + "As a result, you may see the changes you just pushed into it", + "reverted when you run 'git diff' over there, and you may want", + "to run 'git reset --hard' before starting to work to recover.", + "", + "You can set 'receive.denyCurrentBranch' configuration variable to", + "'refuse' in the remote repository to forbid pushing into its", + "current branch." + "", + "To allow pushing into the current branch, you can set it to 'ignore';", + "but this is not recommended unless you arranged to update its work", + "tree to match what you pushed in some other way.", + "", + "To squelch this message, you can set it to 'warn'.", + "", + "Note that the default will change in a future version of git", + "to refuse updating the current branch unless you have the", + "configuration variable set to either 'ignore' or 'warn'." +}; + +static void warn_unconfigured_deny(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++) + warning("%s", warn_unconfigured_deny_msg[i]); +} + +static char *warn_unconfigured_deny_delete_current_msg[] = { + "Deleting the current branch can cause confusion by making the next", + "'git clone' not check out any file.", + "", + "You can set 'receive.denyDeleteCurrent' configuration variable to", + "'refuse' in the remote repository to disallow deleting the current", + "branch.", + "", + "You can set it to 'ignore' to allow such a delete without a warning.", + "", + "To make this warning message less loud, you can set it to 'warn'.", + "", + "Note that the default will change in a future version of git", + "to refuse deleting the current branch unless you have the", + "configuration variable set to either 'ignore' or 'warn'." +}; + +static void warn_unconfigured_deny_delete_current(void) +{ + int i; + for (i = 0; + i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg); + i++) + warning("%s", warn_unconfigured_deny_delete_current_msg[i]); } static const char *update(struct command *cmd) @@ -227,22 +286,20 @@ static const char *update(struct command *cmd) return "funny refname"; } - switch (deny_current_branch) { - case DENY_IGNORE: - break; - case DENY_WARN: - if (!is_ref_checked_out(name)) + if (is_ref_checked_out(name)) { + switch (deny_current_branch) { + case DENY_IGNORE: break; - warning("updating the currently checked out branch; this may" - " cause confusion,\n" - "as the index and working tree do not reflect changes" - " that are now in HEAD."); - break; - case DENY_REFUSE: - if (!is_ref_checked_out(name)) + case DENY_UNCONFIGURED: + case DENY_WARN: + warning("updating the current branch"); + if (deny_current_branch == DENY_UNCONFIGURED) + warn_unconfigured_deny(); break; - error("refusing to update checked out branch: %s", name); - return "branch is currently checked out"; + case DENY_REFUSE: + error("refusing to update checked out branch: %s", name); + return "branch is currently checked out"; + } } if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) { @@ -250,12 +307,30 @@ static const char *update(struct command *cmd) "but I can't find it!", sha1_to_hex(new_sha1)); return "bad pack"; } - if (deny_deletes && is_null_sha1(new_sha1) && - !is_null_sha1(old_sha1) && - !prefixcmp(name, "refs/heads/")) { - error("denying ref deletion for %s", name); - return "deletion prohibited"; + + if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) { + if (deny_deletes && !prefixcmp(name, "refs/heads/")) { + error("denying ref deletion for %s", name); + return "deletion prohibited"; + } + + if (!strcmp(name, head_name)) { + switch (deny_delete_current) { + case DENY_IGNORE: + break; + case DENY_WARN: + case DENY_UNCONFIGURED: + if (deny_delete_current == DENY_UNCONFIGURED) + warn_unconfigured_deny_delete_current(); + warning("deleting the current branch"); + break; + case DENY_REFUSE: + error("refusing to delete the current branch: %s", name); + return "deletion of the current branch prohibited"; + } + } } + if (deny_non_fast_forwards && !is_null_sha1(new_sha1) && !is_null_sha1(old_sha1) && !prefixcmp(name, "refs/heads/")) { @@ -349,6 +424,7 @@ static void run_update_post_hook(struct command *cmd) static void execute_commands(const char *unpacker_error) { struct command *cmd = commands; + unsigned char sha1[20]; if (unpacker_error) { while (cmd) { @@ -358,7 +434,7 @@ static void execute_commands(const char *unpacker_error) return; } - if (run_hook(pre_receive_hook)) { + if (run_receive_hook(pre_receive_hook)) { while (cmd) { cmd->error_string = "pre-receive hook declined"; cmd = cmd->next; @@ -366,6 +442,8 @@ static void execute_commands(const char *unpacker_error) return; } + head_name = resolve_ref("HEAD", sha1, 0, NULL); + while (cmd) { cmd->error_string = update(cmd); cmd = cmd->next; @@ -627,7 +705,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unlink(pack_lockfile); if (report_status) report(unpack_status); - run_hook(post_receive_hook); + run_receive_hook(post_receive_hook); run_update_post_hook(commands); } return 0; diff --git a/builtin-reflog.c b/builtin-reflog.c index d95f515f2e..249ad2a311 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -52,6 +52,7 @@ struct collect_reflog_cb { #define INCOMPLETE (1u<<10) #define STUDYING (1u<<11) +#define REACHABLE (1u<<12) static int tree_is_complete(const unsigned char *sha1) { @@ -209,6 +210,70 @@ static int keep_entry(struct commit **it, unsigned char *sha1) return 1; } +static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1) +{ + /* + * We may or may not have the commit yet - if not, look it + * up using the supplied sha1. + */ + if (!commit) { + if (is_null_sha1(sha1)) + return 0; + + commit = lookup_commit_reference_gently(sha1, 1); + + /* Not a commit -- keep it */ + if (!commit) + return 0; + } + + /* Reachable from the current ref? Don't prune. */ + if (commit->object.flags & REACHABLE) + return 0; + if (in_merge_bases(commit, &cb->ref_commit, 1)) + return 0; + + /* We can't reach it - prune it. */ + return 1; +} + +static void mark_reachable(struct commit *commit, unsigned long expire_limit) +{ + /* + * We need to compute if commit on either side of an reflog + * entry is reachable from the tip of the ref for all entries. + * Mark commits that are reachable from the tip down to the + * time threashold first; we know a commit marked thusly is + * reachable from the tip without running in_merge_bases() + * at all. + */ + struct commit_list *pending = NULL; + + commit_list_insert(commit, &pending); + while (pending) { + struct commit_list *entry = pending; + struct commit_list *parent; + pending = entry->next; + commit = entry->item; + free(entry); + if (commit->object.flags & REACHABLE) + continue; + if (parse_commit(commit)) + continue; + commit->object.flags |= REACHABLE; + if (commit->date < expire_limit) + continue; + parent = commit->parents; + while (parent) { + commit = parent->item; + parent = parent->next; + if (commit->object.flags & REACHABLE) + continue; + commit_list_insert(commit, &pending); + } + } +} + static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, const char *email, unsigned long timestamp, int tz, const char *message, void *cb_data) @@ -230,12 +295,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1, if (timestamp < cb->cmd->expire_unreachable) { if (!cb->ref_commit) goto prune; - if (!old && !is_null_sha1(osha1)) - old = lookup_commit_reference_gently(osha1, 1); - if (!new && !is_null_sha1(nsha1)) - new = lookup_commit_reference_gently(nsha1, 1); - if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) || - (new && !in_merge_bases(new, &cb->ref_commit, 1))) + if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1)) goto prune; } @@ -288,7 +348,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, cb.ref_commit = lookup_commit_reference_gently(sha1, 1); cb.ref = ref; cb.cmd = cmd; + if (cb.ref_commit) + mark_reachable(cb.ref_commit, cmd->expire_total); for_each_reflog_ent(ref, expire_reflog_ent, &cb); + if (cb.ref_commit) + clear_commit_marks(cb.ref_commit, REACHABLE); finish: if (cb.newlog) { if (fclose(cb.newlog)) { diff --git a/builtin-remote.c b/builtin-remote.c index db18bcfc97..ac69d37c8a 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -756,12 +756,17 @@ static int prune(int argc, const char **argv) OPT_END() }; struct ref_states states; + const char *dangling_msg; argc = parse_options(argc, argv, options, builtin_remote_usage, 0); if (argc < 1) usage_with_options(builtin_remote_usage, options); + dangling_msg = (dry_run + ? " %s will become dangling!\n" + : " %s has become dangling!\n"); + memset(&states, 0, sizeof(states)); for (; argc; argc--, argv++) { int i; @@ -784,6 +789,7 @@ static int prune(int argc, const char **argv) printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", abbrev_ref(refname, "refs/remotes/")); + warn_dangling_symref(dangling_msg, refname); } /* NEEDSWORK: free remote */ diff --git a/builtin-rerere.c b/builtin-rerere.c index d4dec6b715..bd8fc77a7a 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "dir.h" #include "string-list.h" #include "rerere.h" #include "xdiff/xdiff.h" @@ -59,17 +60,15 @@ static void garbage_collect(struct string_list *rr) git_config(git_rerere_gc_config, NULL); dir = opendir(git_path("rr-cache")); while ((e = readdir(dir))) { - const char *name = e->d_name; - if (name[0] == '.' && - (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) + if (is_dot_or_dotdot(e->d_name)) continue; - then = rerere_created_at(name); + then = rerere_created_at(e->d_name); if (!then) continue; - cutoff = (has_resolution(name) + cutoff = (has_resolution(e->d_name) ? cutoff_resolve : cutoff_noresolve); if (then < now - cutoff * 86400) - string_list_append(name, &to_remove); + string_list_append(e->d_name, &to_remove); } for (i = 0; i < to_remove.nr; i++) unlink_rr_item(to_remove.items[i].string); diff --git a/builtin-reset.c b/builtin-reset.c index 9514b77f8c..c0cb915c26 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -20,11 +20,14 @@ #include "parse-options.h" static const char * const git_reset_usage[] = { - "git reset [--mixed | --soft | --hard] [-q] [<commit>]", + "git reset [--mixed | --soft | --hard | --merge] [-q] [<commit>]", "git reset [--mixed] <commit> [--] <paths>...", NULL }; +enum reset_type { MIXED, SOFT, HARD, MERGE, NONE }; +static const char *reset_type_names[] = { "mixed", "soft", "hard", "merge", NULL }; + static char *args_to_str(const char **argv) { char *buf = NULL; @@ -49,7 +52,7 @@ static inline int is_merge(void) return !access(git_path("MERGE_HEAD"), F_OK); } -static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int quiet) +static int reset_index_file(const unsigned char *sha1, int reset_type, int quiet) { int i = 0; const char *args[6]; @@ -57,9 +60,17 @@ static int reset_index_file(const unsigned char *sha1, int is_hard_reset, int qu args[i++] = "read-tree"; if (!quiet) args[i++] = "-v"; - args[i++] = "--reset"; - if (is_hard_reset) + switch (reset_type) { + case MERGE: args[i++] = "-u"; + args[i++] = "-m"; + break; + case HARD: + args[i++] = "-u"; + /* fallthrough */ + default: + args[i++] = "--reset"; + } args[i++] = sha1_to_hex(sha1); args[i] = NULL; @@ -169,9 +180,6 @@ static void prepend_reflog_action(const char *action, char *buf, size_t size) warning("Reflog action message too long: %.*s...", 50, buf); } -enum reset_type { MIXED, SOFT, HARD, NONE }; -static const char *reset_type_names[] = { "mixed", "soft", "hard", NULL }; - int cmd_reset(int argc, const char **argv, const char *prefix) { int i = 0, reset_type = NONE, update_ref_status = 0, quiet = 0; @@ -186,6 +194,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) OPT_SET_INT(0, "soft", &reset_type, "reset only HEAD", SOFT), OPT_SET_INT(0, "hard", &reset_type, "reset HEAD, index and working tree", HARD), + OPT_SET_INT(0, "merge", &reset_type, + "reset HEAD, index and working tree", MERGE), OPT_BOOLEAN('q', NULL, &quiet, "disable showing new HEAD in hard reset and progress message"), OPT_END() @@ -266,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (is_merge() || read_cache() < 0 || unmerged_cache()) die("Cannot do a soft reset in the middle of a merge."); } - else if (reset_index_file(sha1, (reset_type == HARD), quiet)) + else if (reset_index_file(sha1, reset_type, quiet)) die("Could not reset index file to revision '%s'.", rev); /* Any resets update HEAD to the head being switched to, diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 436afa45f5..0af7cd94f9 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -168,27 +168,29 @@ static void finish_commit(struct commit *commit) commit->buffer = NULL; } -static void finish_object(struct object_array_entry *p) +static void finish_object(struct object *obj, const struct name_path *path, const char *name) { - if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1)) - die("missing blob object '%s'", sha1_to_hex(p->item->sha1)); + if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1)) + die("missing blob object '%s'", sha1_to_hex(obj->sha1)); } -static void show_object(struct object_array_entry *p) +static void show_object(struct object *obj, const struct name_path *path, const char *component) { + char *name = path_name(path, component); /* An object with name "foo\n0000000..." can be used to * confuse downstream "git pack-objects" very badly. */ - const char *ep = strchr(p->name, '\n'); + const char *ep = strchr(name, '\n'); - finish_object(p); + finish_object(obj, path, name); if (ep) { - printf("%s %.*s\n", sha1_to_hex(p->item->sha1), - (int) (ep - p->name), - p->name); + printf("%s %.*s\n", sha1_to_hex(obj->sha1), + (int) (ep - name), + name); } else - printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name); + printf("%s %s\n", sha1_to_hex(obj->sha1), name); + free(name); } static void show_edge(struct commit *commit) diff --git a/builtin-revert.c b/builtin-revert.c index 09d08fa3e3..3f2614e1bb 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -223,17 +223,6 @@ static char *help_msg(const unsigned char *sha1) return helpbuf; } -static int index_is_dirty(void) -{ - struct rev_info rev; - init_revisions(&rev, NULL); - setup_revisions(0, NULL, &rev, "HEAD"); - DIFF_OPT_SET(&rev.diffopt, QUIET); - DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); - run_diff_index(&rev, 1); - return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES); -} - static struct tree *empty_tree(void) { struct tree *tree = xcalloc(1, sizeof(struct tree)); @@ -279,7 +268,7 @@ static int revert_or_cherry_pick(int argc, const char **argv) } else { if (get_sha1("HEAD", head)) die ("You do not have a valid HEAD"); - if (index_is_dirty()) + if (index_differs_from("HEAD", 0)) die ("Dirty index: cannot %s", me); } discard_cache(); diff --git a/builtin-shortlog.c b/builtin-shortlog.c index e49290687f..badd912038 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -39,8 +39,8 @@ static void insert_one_record(struct shortlog *log, const char *dot3 = log->common_repo_prefix; char *buffer, *p; struct string_list_item *item; - struct string_list *onelines; char namebuf[1024]; + char emailbuf[1024]; size_t len; const char *eol; const char *boemail, *eoemail; @@ -52,7 +52,19 @@ static void insert_one_record(struct shortlog *log, eoemail = strchr(boemail, '>'); if (!eoemail) return; - if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) { + + /* copy author name to namebuf, to support matching on both name and email */ + memcpy(namebuf, author, boemail - author); + len = boemail - author; + while(len > 0 && isspace(namebuf[len-1])) + len--; + namebuf[len] = 0; + + /* copy email name to emailbuf, to allow email replacement as well */ + memcpy(emailbuf, boemail+1, eoemail - boemail); + emailbuf[eoemail - boemail - 1] = 0; + + if (!map_user(&log->mailmap, emailbuf, sizeof(emailbuf), namebuf, sizeof(namebuf))) { while (author < boemail && isspace(*author)) author++; for (len = 0; @@ -68,16 +80,13 @@ static void insert_one_record(struct shortlog *log, if (log->email) { size_t room = sizeof(namebuf) - len - 1; - int maillen = eoemail - boemail + 1; - snprintf(namebuf + len, room, " %.*s", maillen, boemail); + int maillen = strlen(emailbuf); + snprintf(namebuf + len, room, " <%.*s>", maillen, emailbuf); } - buffer = xstrdup(namebuf); - item = string_list_insert(buffer, &log->list); + item = string_list_insert(namebuf, &log->list); if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); - else - free(buffer); /* Skip any leading whitespace, including any blank lines. */ while (*oneline && isspace(*oneline)) @@ -107,16 +116,7 @@ static void insert_one_record(struct shortlog *log, } } - onelines = item->util; - if (onelines->nr >= onelines->alloc) { - onelines->alloc = alloc_nr(onelines->nr); - onelines->items = xrealloc(onelines->items, - onelines->alloc - * sizeof(struct string_list_item)); - } - - onelines->items[onelines->nr].util = NULL; - onelines->items[onelines->nr++].string = buffer; + string_list_append(buffer, item->util); } static void read_from_stdin(struct shortlog *log) @@ -232,7 +232,7 @@ void shortlog_init(struct shortlog *log) { memset(log, 0, sizeof(*log)); - read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix); + read_mailmap(&log->mailmap, &log->common_repo_prefix); log->list.strdup_strings = 1; log->wrap = DEFAULT_WRAPLEN; @@ -261,6 +261,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) struct parse_opt_ctx_t ctx; prefix = setup_git_directory_gently(&nongit); + git_config(git_default_config, NULL); shortlog_init(&log); init_revisions(&rev, prefix); parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH | @@ -326,13 +327,12 @@ void shortlog_output(struct shortlog *log) } onelines->strdup_strings = 1; - string_list_clear(onelines, 1); + string_list_clear(onelines, 0); free(onelines); log->list.items[i].util = NULL; } log->list.strdup_strings = 1; string_list_clear(&log->list, 1); - log->mailmap.strdup_strings = 1; - string_list_clear(&log->mailmap, 1); + clear_mailmap(&log->mailmap); } diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index bfc78bb3f6..6ae6bcc0e8 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -44,6 +44,9 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) check_symref(argv[0], quiet); break; case 2: + if (!strcmp(argv[0], "HEAD") && + prefixcmp(argv[1], "refs/")) + die("Refusing to point HEAD outside of refs/"); create_symref(argv[0], argv[1], msg); break; default: diff --git a/builtin-tag.c b/builtin-tag.c index a398499891..01e73747d0 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-update-index.c b/builtin-update-index.c index daca0f775e..dd43d5bef4 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -486,7 +486,7 @@ static int unresolve_one(const char *path) static void read_head_pointers(void) { if (read_ref("HEAD", head_sha1)) - die("No HEAD -- no initial commit yet?\n"); + die("No HEAD -- no initial commit yet?"); if (read_ref("MERGE_HEAD", merge_head_sha1)) { fprintf(stderr, "Not in the middle of a merge.\n"); exit(0); diff --git a/builtin-verify-pack.c b/builtin-verify-pack.c index 25a29f11a4..0ee0a9af60 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) { @@ -167,6 +167,32 @@ int list_bundle_refs(struct bundle_header *header, int argc, const char **argv) return list_refs(&header->references, argc, argv); } +static int is_tag_in_date_range(struct object *tag, struct rev_info *revs) +{ + unsigned long size; + enum object_type type; + char *buf, *line, *lineend; + unsigned long date; + + if (revs->max_age == -1 && revs->min_age == -1) + return 1; + + buf = read_sha1_file(tag->sha1, &type, &size); + if (!buf) + return 1; + line = memmem(buf, size, "\ntagger ", 8); + if (!line++) + return 1; + lineend = memchr(line, buf + size - line, '\n'); + line = memchr(line, lineend ? lineend - line : buf + size - line, '>'); + if (!line++) + return 1; + date = strtoul(line, NULL, 10); + free(buf); + return (revs->max_age == -1 || revs->max_age < date) && + (revs->min_age == -1 || revs->min_age > date); +} + int create_bundle(struct bundle_header *header, const char *path, int argc, const char **argv) { @@ -257,6 +283,12 @@ int create_bundle(struct bundle_header *header, const char *path, flag = 0; display_ref = (flag & REF_ISSYMREF) ? e->name : ref; + if (e->item->type == OBJ_TAG && + !is_tag_in_date_range(e->item, &revs)) { + e->item->flags |= UNINTERESTING; + continue; + } + /* * Make sure the refs we wrote out is correct; --max-count and * other limiting options could have prevented all the tips diff --git a/cache-tree.c b/cache-tree.c index 3d8f218a5f..37bf35e636 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -1,5 +1,6 @@ #include "cache.h" #include "tree.h" +#include "tree-walk.h" #include "cache-tree.h" #ifndef DEBUG @@ -591,3 +592,36 @@ int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix) return 0; } + +static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree) +{ + struct tree_desc desc; + struct name_entry entry; + int cnt; + + hashcpy(it->sha1, tree->object.sha1); + init_tree_desc(&desc, tree->buffer, tree->size); + cnt = 0; + while (tree_entry(&desc, &entry)) { + if (!S_ISDIR(entry.mode)) + cnt++; + else { + struct cache_tree_sub *sub; + struct tree *subtree = lookup_tree(entry.sha1); + if (!subtree->object.parsed) + parse_tree(subtree); + sub = cache_tree_sub(it, entry.path); + sub->cache_tree = cache_tree(); + prime_cache_tree_rec(sub->cache_tree, subtree); + cnt += sub->cache_tree->entry_count; + } + } + it->entry_count = cnt; +} + +void prime_cache_tree(struct cache_tree **it, struct tree *tree) +{ + cache_tree_free(it); + *it = cache_tree(); + prime_cache_tree_rec(*it, tree); +} diff --git a/cache-tree.h b/cache-tree.h index cf8b790874..e958835236 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -1,6 +1,8 @@ #ifndef CACHE_TREE_H #define CACHE_TREE_H +#include "tree.h" + struct cache_tree; struct cache_tree_sub { struct cache_tree *cache_tree; @@ -33,4 +35,6 @@ int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int) #define WRITE_TREE_PREFIX_ERROR (-3) int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix); +void prime_cache_tree(struct cache_tree **, struct tree *); + #endif @@ -613,7 +613,8 @@ enum sharedrepo { PERM_EVERYBODY = 0664, }; int git_config_perm(const char *var, const char *value); -int adjust_shared_perm(const char *path); +int set_shared_perm(const char *path, int mode); +#define adjust_shared_perm(path) set_shared_perm((path), 0) int safe_create_leading_directories(char *path); int safe_create_leading_directories_const(const char *path); char *enter_repo(char *path, int strict); @@ -627,6 +628,7 @@ const char *make_nonrelative_path(const char *path); const char *make_relative_path(const char *abs, const char *base); int normalize_path_copy(char *dst, const char *src); int longest_ancestor_length(const char *path, const char *prefix_list); +char *strip_path_suffix(const char *path, const char *suffix); /* Read and unpack a sha1 file into memory, write memory to a sha1 file */ extern int sha1_object_info(const unsigned char *, unsigned long *); @@ -636,9 +638,6 @@ extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsig extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *); extern int force_object_loose(const unsigned char *sha1, time_t mtime); -/* just like read_sha1_file(), but non fatal in presence of bad objects */ -extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); - /* global flag to enable extra checks when accessing packed objects */ extern int do_check_packed_object_crc; @@ -671,6 +670,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[]; @@ -697,7 +697,8 @@ enum date_mode { DATE_SHORT, DATE_LOCAL, DATE_ISO8601, - DATE_RFC2822 + DATE_RFC2822, + DATE_RAW }; const char *show_date(unsigned long time, int timezone, enum date_mode mode); @@ -725,6 +726,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; @@ -866,6 +871,7 @@ extern int user_ident_explicitly_given; extern const char *git_commit_encoding; extern const char *git_log_output_encoding; +extern const char *git_mailmap_file; /* IO helper functions */ extern void maybe_flush_or_die(FILE *, const char *); @@ -941,7 +947,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); @@ -41,29 +41,40 @@ static int parse_attr(const char *name, int len) void color_parse(const char *value, const char *var, char *dst) { + color_parse_mem(value, strlen(value), var, dst); +} + +void color_parse_mem(const char *value, int value_len, const char *var, + char *dst) +{ const char *ptr = value; + int len = value_len; int attr = -1; int fg = -2; int bg = -2; - if (!strcasecmp(value, "reset")) { + if (!strncasecmp(value, "reset", len)) { strcpy(dst, "\033[m"); return; } /* [fg [bg]] [attr] */ - while (*ptr) { + while (len > 0) { const char *word = ptr; - int val, len = 0; + int val, wordlen = 0; - while (word[len] && !isspace(word[len])) - len++; + while (len > 0 && !isspace(word[wordlen])) { + wordlen++; + len--; + } - ptr = word + len; - while (*ptr && isspace(*ptr)) + ptr = word + wordlen; + while (len > 0 && isspace(*ptr)) { ptr++; + len--; + } - val = parse_color(word, len); + val = parse_color(word, wordlen); if (val >= -1) { if (fg == -2) { fg = val; @@ -75,7 +86,7 @@ void color_parse(const char *value, const char *var, char *dst) } goto bad; } - val = parse_attr(word, len); + val = parse_attr(word, wordlen); if (val < 0 || attr != -1) goto bad; attr = val; @@ -115,7 +126,7 @@ void color_parse(const char *value, const char *var, char *dst) *dst = 0; return; bad: - die("bad config value '%s' for variable '%s'", value, var); + die("bad color value '%.*s' for variable '%s'", value_len, value, var); } int git_config_colorbool(const char *var, const char *value, int stdout_is_tty) @@ -191,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; +} + + @@ -16,8 +16,10 @@ extern int git_use_color_default; int git_color_default_config(const char *var, const char *value, void *cb); int git_config_colorbool(const char *var, const char *value, int stdout_is_tty); -void color_parse(const char *var, const char *value, char *dst); +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/memmem.c b/compat/memmem.c index cd0d877364..56bcb4277f 100644 --- a/compat/memmem.c +++ b/compat/memmem.c @@ -5,6 +5,8 @@ void *gitmemmem(const void *haystack, size_t haystack_len, { const char *begin = haystack; const char *last_possible = begin + haystack_len - needle_len; + const char *tail = needle; + char point; /* * The first occurrence of the empty string is deemed to occur at @@ -20,8 +22,9 @@ void *gitmemmem(const void *haystack, size_t haystack_len, if (haystack_len < needle_len) return NULL; + point = *tail++; for (; begin <= last_possible; begin++) { - if (!memcmp(begin, needle, needle_len)) + if (*begin == point && !memcmp(begin + 1, tail, needle_len - 1)) return (void *)begin; } diff --git a/compat/mingw.h b/compat/mingw.h index 4f275cb8e6..a255898801 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 @@ -565,6 +565,15 @@ static int git_default_branch_config(const char *var, const char *value) return 0; } +static int git_default_mailmap_config(const char *var, const char *value) +{ + if (!strcmp(var, "mailmap.file")) + return git_config_string(&git_mailmap_file, var, value); + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + int git_default_config(const char *var, const char *value, void *dummy) { if (!prefixcmp(var, "core.")) @@ -579,6 +588,9 @@ int git_default_config(const char *var, const char *value, void *dummy) if (!prefixcmp(var, "branch.")) return git_default_branch_config(var, value); + if (!prefixcmp(var, "mailmap.")) + return git_default_mailmap_config(var, value); + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; @@ -942,7 +954,7 @@ int git_config_set_multivar(const char* key, const char* value, lock = xcalloc(sizeof(struct lock_file), 1); fd = hold_lock_file_for_update(lock, config_filename, 0); if (fd < 0) { - error("could not lock config file %s", config_filename); + error("could not lock config file %s: %s", config_filename, strerror(errno)); free(store.key); ret = -1; goto out_free; diff --git a/config.mak.in b/config.mak.in index 14dfb21fa5..7cce0c12d5 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@ @@ -52,4 +52,5 @@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ FREAD_READS_DIRECTORIES=@FREAD_READS_DIRECTORIES@ SNPRINTF_RETURNS_BOGUS=@SNPRINTF_RETURNS_BOGUS@ NO_PTHREADS=@NO_PTHREADS@ +THREADED_DELTA_SEARCH=@THREADED_DELTA_SEARCH@ PTHREAD_LIBS=@PTHREAD_LIBS@ diff --git a/configure.ac b/configure.ac index 0a5fc8c6f6..082a03d3cf 100644 --- a/configure.ac +++ b/configure.ac @@ -114,31 +114,31 @@ AC_MSG_NOTICE([CHECKS for programs]) # AC_PROG_CC([cc gcc]) # which switch to pass runtime path to dynamic libraries to the linker -AC_CACHE_CHECK([if linker supports -R], ld_dashr, [ +AC_CACHE_CHECK([if linker supports -R], git_cv_ld_dashr, [ SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -R /" - AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_dashr=yes], [ld_dashr=no]) + AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_dashr=yes], [git_cv_ld_dashr=no]) LDFLAGS="${SAVE_LDFLAGS}" ]) -if test "$ld_dashr" = "yes"; then +if test "$git_cv_ld_dashr" = "yes"; then AC_SUBST(CC_LD_DYNPATH, [-R]) else - AC_CACHE_CHECK([if linker supports -Wl,-rpath,], ld_wl_rpath, [ + AC_CACHE_CHECK([if linker supports -Wl,-rpath,], git_cv_ld_wl_rpath, [ SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -Wl,-rpath,/" - AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_wl_rpath=yes], [ld_wl_rpath=no]) + AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_wl_rpath=yes], [git_cv_ld_wl_rpath=no]) LDFLAGS="${SAVE_LDFLAGS}" ]) - if test "$ld_wl_rpath" = "yes"; then + if test "$git_cv_ld_wl_rpath" = "yes"; then AC_SUBST(CC_LD_DYNPATH, [-Wl,-rpath,]) else - AC_CACHE_CHECK([if linker supports -rpath], ld_rpath, [ + AC_CACHE_CHECK([if linker supports -rpath], git_cv_ld_rpath, [ SAVE_LDFLAGS="${LDFLAGS}" LDFLAGS="${SAVE_LDFLAGS} -rpath /" - AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [ld_rpath=yes], [ld_rpath=no]) + AC_LINK_IFELSE(AC_LANG_PROGRAM([], []), [git_cv_ld_rpath=yes], [git_cv_ld_rpath=no]) LDFLAGS="${SAVE_LDFLAGS}" ]) - if test "$ld_rpath" = "yes"; then + if test "$git_cv_ld_rpath" = "yes"; then AC_SUBST(CC_LD_DYNPATH, [-rpath]) else AC_MSG_WARN([linker does not support runtime path to dynamic libraries]) @@ -492,7 +492,8 @@ AC_SUBST(NO_MKDTEMP) # # Define NO_PTHREADS if we do not have pthreads # -# Define PTHREAD_LIBS to the linker flag used for Pthread support. +# Define PTHREAD_LIBS to the linker flag used for Pthread support and define +# THREADED_DELTA_SEARCH if Pthreads are available. AC_LANG_CONFTEST([AC_LANG_PROGRAM( [[#include <pthread.h>]], [[pthread_mutex_t test_mutex;]] @@ -500,16 +501,19 @@ AC_LANG_CONFTEST([AC_LANG_PROGRAM( ${CC} -pthread conftest.c -o conftest.o > /dev/null 2>&1 if test $? -eq 0;then PTHREAD_LIBS="-pthread" + THREADED_DELTA_SEARCH=YesPlease else ${CC} -lpthread conftest.c -o conftest.o > /dev/null 2>&1 if test $? -eq 0;then PTHREAD_LIBS="-lpthread" + THREADED_DELTA_SEARCH=YesPlease else NO_PTHREADS=UnfortunatelyYes fi fi AC_SUBST(PTHREAD_LIBS) AC_SUBST(NO_PTHREADS) +AC_SUBST(THREADED_DELTA_SEARCH) ## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE @@ -315,7 +315,7 @@ static int git_tcp_connect_sock(char *host, int flags) /* Not numeric */ struct servent *se = getservbyname(port,"tcp"); if ( !se ) - die("Unknown port %s\n", port); + die("Unknown port %s", port); nport = se->s_port; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 3889cfb5aa..8431837f97 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1,3 +1,4 @@ +#!bash # # bash completion support for core Git. # @@ -33,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 @@ -50,9 +57,11 @@ case "$COMP_WORDBREAKS" in *) COMP_WORDBREAKS="$COMP_WORDBREAKS:" esac +# __gitdir accepts 0 or 1 arguments (i.e., location) +# returns location of .git repo __gitdir () { - if [ -z "$1" ]; then + if [ -z "${1-}" ]; then if [ -n "$__git_dir" ]; then echo "$__git_dir" elif [ -d .git ]; then @@ -67,6 +76,8 @@ __gitdir () fi } +# __git_ps1 accepts 0 or 1 arguments (i.e., format string) +# returns text to add to bash PS1 prompt (includes branch name) __git_ps1 () { local g="$(git rev-parse --git-dir 2>/dev/null)" @@ -111,14 +122,31 @@ __git_ps1 () fi fi - if [ -n "$1" ]; then - printf "$1" "${b##refs/heads/}$r" + 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/}$w$i$r" else - printf " (%s)" "${b##refs/heads/}$r" + printf " (%s)" "${b##refs/heads/}$w$i$r" fi fi } +# __gitcomp_1 requires 2 arguments __gitcomp_1 () { local c IFS=' '$'\t'$'\n' @@ -131,6 +159,8 @@ __gitcomp_1 () done } +# __gitcomp accepts 1, 2, 3, or 4 arguments +# generates completion reply with compgen __gitcomp () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -143,22 +173,23 @@ __gitcomp () ;; *) local IFS=$'\n' - COMPREPLY=($(compgen -P "$2" \ - -W "$(__gitcomp_1 "$1" "$4")" \ + COMPREPLY=($(compgen -P "${2-}" \ + -W "$(__gitcomp_1 "${1-}" "${4-}")" \ -- "$cur")) ;; esac } +# __git_heads accepts 0 or 1 arguments (to pass to __gitdir) __git_heads () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local cmd i is_hash=y dir="$(__gitdir "${1-}")" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/heads return fi - for i in $(git ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "${1-}" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -168,15 +199,16 @@ __git_heads () done } +# __git_tags accepts 0 or 1 arguments (to pass to __gitdir) __git_tags () { - local cmd i is_hash=y dir="$(__gitdir "$1")" + local cmd i is_hash=y dir="$(__gitdir "${1-}")" if [ -d "$dir" ]; then git --git-dir="$dir" for-each-ref --format='%(refname:short)' \ refs/tags return fi - for i in $(git ls-remote "$1" 2>/dev/null); do + for i in $(git ls-remote "${1-}" 2>/dev/null); do case "$is_hash,$i" in y,*) is_hash=n ;; n,*^{}) is_hash=y ;; @@ -186,9 +218,10 @@ __git_tags () done } +# __git_refs accepts 0 or 1 arguments (to pass to __gitdir) __git_refs () { - local i is_hash=y dir="$(__gitdir "$1")" + local i is_hash=y dir="$(__gitdir "${1-}")" local cur="${COMP_WORDS[COMP_CWORD]}" format refs if [ -d "$dir" ]; then case "$cur" in @@ -218,6 +251,7 @@ __git_refs () done } +# __git_refs2 requires 1 argument (to pass to __git_refs) __git_refs2 () { local i @@ -226,6 +260,7 @@ __git_refs2 () done } +# __git_refs_remotes requires 1 argument (to pass to ls-remote) __git_refs_remotes () { local cmd i is_hash=y @@ -470,6 +505,7 @@ __git_aliases () done } +# __git_aliased_command requires 1 argument __git_aliased_command () { local word cmdline=$(git --git-dir="$(__gitdir)" \ @@ -482,6 +518,7 @@ __git_aliased_command () done } +# __git_find_subcommand requires 1 argument __git_find_subcommand () { local word subcommand c=1 @@ -563,7 +600,7 @@ _git_add () --*) __gitcomp " --interactive --refresh --patch --update --dry-run - --ignore-errors + --ignore-errors --intent-to-add " return esac @@ -628,7 +665,6 @@ _git_branch () done case "${COMP_WORDS[COMP_CWORD]}" in - --*=*) COMPREPLY=() ;; --*) __gitcomp " --color --no-color --verbose --abbrev= --no-abbrev @@ -759,23 +795,30 @@ _git_describe () __gitcomp "$(__git_refs)" } -_git_diff () -{ - __git_has_doubledash && return - - local cur="${COMP_WORDS[COMP_CWORD]}" - case "$cur" in - --*) - __gitcomp "--cached --stat --numstat --shortstat --summary +__git_diff_common_options="--stat --numstat --shortstat --summary --patch-with-stat --name-only --name-status --color --no-color --color-words --no-renames --check --full-index --binary --abbrev --diff-filter= - --find-copies-harder --pickaxe-all --pickaxe-regex + --find-copies-harder --text --ignore-space-at-eol --ignore-space-change --ignore-all-space --exit-code --quiet --ext-diff --no-ext-diff --no-prefix --src-prefix= --dst-prefix= + --inter-hunk-context= + --patience + --raw +" + +_git_diff () +{ + __git_has_doubledash && return + + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "--cached --pickaxe-all --pickaxe-regex --base --ours --theirs + $__git_diff_common_options " return ;; @@ -823,6 +866,8 @@ _git_format_patch () --not --all --cover-letter --no-prefix --src-prefix= --dst-prefix= + --inline --suffix= --ignore-if-in-upstream + --subject-prefix= " return ;; @@ -930,6 +975,29 @@ _git_ls_tree () __git_complete_file } +# Options that go well for log, shortlog and gitk +__git_log_common_options=" + --not --all + --branches --tags --remotes + --first-parent --no-merges + --max-count= + --max-age= --since= --after= + --min-age= --until= --before= +" +# Options that go well for log and gitk (not shortlog) +__git_log_gitk_options=" + --dense --sparse --full-history + --simplify-merges --simplify-by-decoration + --left-right +" +# Options that go well for log and shortlog (not gitk) +__git_log_shortlog_options=" + --author= --committer= --grep= + --all-match +" + +__git_log_pretty_formats="oneline short medium full fuller email raw format:" + _git_log () { __git_has_doubledash && return @@ -942,8 +1010,7 @@ _git_log () fi case "$cur" in --pretty=*) - __gitcomp " - oneline short medium full fuller email raw + __gitcomp "$__git_log_pretty_formats " "" "${cur##--pretty=}" return ;; @@ -955,23 +1022,22 @@ _git_log () ;; --*) __gitcomp " - --max-count= --max-age= --since= --after= - --min-age= --before= --until= + $__git_log_common_options + $__git_log_shortlog_options + $__git_log_gitk_options --root --topo-order --date-order --reverse - --no-merges --follow + --follow --abbrev-commit --abbrev= --relative-date --date= - --author= --committer= --grep= - --all-match - --pretty= --name-status --name-only --raw - --not --all - --left-right --cherry-pick + --pretty= + --cherry-pick --graph - --stat --numstat --shortstat - --decorate --diff-filter= - --color-words --walk-reflogs - --parents --children --full-history + --decorate + --walk-reflogs + --parents --children $merge + $__git_diff_common_options + --pickaxe-all --pickaxe-regex " return ;; @@ -995,6 +1061,7 @@ _git_merge () --*) __gitcomp " --no-commit --no-stat --log --no-log --squash --strategy + --commit --stat --no-squash --ff --no-ff " return esac @@ -1154,10 +1221,14 @@ _git_config () __gitcomp "$(__git_merge_strategies)" return ;; - color.branch|color.diff|color.status) + color.branch|color.diff|color.interactive|color.status|color.ui) __gitcomp "always never auto" return ;; + color.pager) + __gitcomp "false true" + return + ;; color.*.*) __gitcomp " normal black red green yellow blue magenta cyan white @@ -1372,7 +1443,7 @@ _git_config () _git_remote () { - local subcommands="add rm show prune update" + local subcommands="add rename rm show prune update" local subcommand="$(__git_find_subcommand "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" @@ -1380,7 +1451,7 @@ _git_remote () fi case "$subcommand" in - rm|show|prune) + rename|rm|show|prune) __gitcomp "$(__git_remotes)" ;; update) @@ -1408,7 +1479,7 @@ _git_reset () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - __gitcomp "--mixed --hard --soft" + __gitcomp "--merge --mixed --hard --soft" return ;; esac @@ -1449,12 +1520,8 @@ _git_shortlog () case "$cur" in --*) __gitcomp " - --max-count= --max-age= --since= --after= - --min-age= --before= --until= - --no-merges - --author= --committer= --grep= - --all-match - --not --all + $__git_log_common_options + $__git_log_shortlog_options --numbered --summary " return @@ -1470,13 +1537,14 @@ _git_show () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --pretty=*) - __gitcomp " - oneline short medium full fuller email raw + __gitcomp "$__git_log_pretty_formats " "" "${cur##--pretty=}" return ;; --*) - __gitcomp "--pretty=" + __gitcomp "--pretty= + $__git_diff_common_options + " return ;; esac @@ -1552,7 +1620,8 @@ _git_svn () local subcommands=" init fetch clone rebase dcommit log find-rev set-tree commit-diff info create-ignore propget - proplist show-ignore show-externals + proplist show-ignore show-externals branch tag blame + migrate " local subcommand="$(__git_find_subcommand "$subcommands")" if [ -z "$subcommand" ]; then @@ -1563,13 +1632,15 @@ _git_svn () --follow-parent --authors-file= --repack= --no-metadata --use-svm-props --use-svnsync-props --log-window-size= --no-checkout --quiet - --repack-flags --user-log-author $remote_opts + --repack-flags --use-log-author --localtime + --ignore-paths= $remote_opts " local init_opts=" --template= --shared= --trunk= --tags= --branches= --stdlayout --minimize-url --no-metadata --use-svm-props --use-svnsync-props - --rewrite-root= $remote_opts + --rewrite-root= --prefix= --use-log-author + --add-author-from $remote_opts " local cmt_opts=" --edit --rmdir --find-copies-harder --copy-similarity= @@ -1589,7 +1660,8 @@ _git_svn () dcommit,--*) __gitcomp " --merge --strategy= --verbose --dry-run - --fetch-all --no-rebase $cmt_opts $fc_opts + --fetch-all --no-rebase --commit-url + --revision $cmt_opts $fc_opts " ;; set-tree,--*) @@ -1603,13 +1675,13 @@ _git_svn () __gitcomp " --limit= --revision= --verbose --incremental --oneline --show-commit --non-recursive - --authors-file= + --authors-file= --color " ;; rebase,--*) __gitcomp " --merge --verbose --strategy= --local - --fetch-all $fc_opts + --fetch-all --dry-run $fc_opts " ;; commit-diff,--*) @@ -1618,6 +1690,21 @@ _git_svn () info,--*) __gitcomp "--url" ;; + branch,--*) + __gitcomp "--dry-run --message --tag" + ;; + tag,--*) + __gitcomp "--dry-run --message" + ;; + blame,--*) + __gitcomp "--git-format" + ;; + migrate,--*) + __gitcomp " + --config-dir= --ignore-paths= --minimize + --no-auth-cache --username= + " + ;; *) COMPREPLY=() ;; @@ -1677,7 +1764,6 @@ _git () if [ -z "$command" ]; then case "${COMP_WORDS[COMP_CWORD]}" in - --*=*) COMPREPLY=() ;; --*) __gitcomp " --paginate --no-pager @@ -1741,6 +1827,7 @@ _git () show) _git_show ;; show-branch) _git_show_branch ;; stash) _git_stash ;; + stage) _git_add ;; submodule) _git_submodule ;; svn) _git_svn ;; tag) _git_tag ;; @@ -1761,20 +1848,27 @@ _gitk () fi case "$cur" in --*) - __gitcomp "--not --all $merge" + __gitcomp " + $__git_log_common_options + $__git_log_gitk_options + $merge + " return ;; esac __git_complete_revlist } -complete -o default -o nospace -F _git git -complete -o default -o nospace -F _gitk gitk +complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \ + || complete -o default -o nospace -F _git git +complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \ + || complete -o default -o nospace -F _gitk gitk # The following are necessary only for Cygwin, and only are needed # when the user has tab-completed the executable name and consequently # included the '.exe' suffix. # if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then -complete -o default -o nospace -F _git git.exe +complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \ + || complete -o default -o nospace -F _git git.exe fi diff --git a/contrib/difftool/git-difftool b/contrib/difftool/git-difftool new file mode 100755 index 0000000000..0cda3d2eea --- /dev/null +++ b/contrib/difftool/git-difftool @@ -0,0 +1,73 @@ +#!/usr/bin/env perl +# Copyright (c) 2009 David Aguilar +# +# This is a wrapper around the GIT_EXTERNAL_DIFF-compatible +# git-difftool-helper script. This script exports +# GIT_EXTERNAL_DIFF and GIT_PAGER for use by git, and +# GIT_DIFFTOOL_NO_PROMPT and GIT_MERGE_TOOL for use by git-difftool-helper. +# Any arguments that are unknown to this script are forwarded to 'git diff'. + +use strict; +use warnings; +use Cwd qw(abs_path); +use File::Basename qw(dirname); + +my $DIR = abs_path(dirname($0)); + + +sub usage +{ + print << 'USAGE'; +usage: git difftool [--tool=<tool>] [--no-prompt] ["git diff" options] +USAGE + exit 1; +} + +sub setup_environment +{ + $ENV{PATH} = "$DIR:$ENV{PATH}"; + $ENV{GIT_PAGER} = ''; + $ENV{GIT_EXTERNAL_DIFF} = 'git-difftool-helper'; +} + +sub exe +{ + my $exe = shift; + return defined $ENV{COMSPEC} ? "$exe.exe" : $exe; +} + +sub generate_command +{ + my @command = (exe('git'), 'diff'); + my $skip_next = 0; + my $idx = -1; + for my $arg (@ARGV) { + $idx++; + if ($skip_next) { + $skip_next = 0; + next; + } + if ($arg eq '-t' or $arg eq '--tool') { + usage() if $#ARGV <= $idx; + $ENV{GIT_MERGE_TOOL} = $ARGV[$idx + 1]; + $skip_next = 1; + next; + } + if ($arg =~ /^--tool=/) { + $ENV{GIT_MERGE_TOOL} = substr($arg, 7); + next; + } + if ($arg eq '--no-prompt') { + $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; + next; + } + if ($arg eq '-h' or $arg eq '--help') { + usage(); + } + push @command, $arg; + } + return @command +} + +setup_environment(); +exec(generate_command()); diff --git a/contrib/difftool/git-difftool-helper b/contrib/difftool/git-difftool-helper new file mode 100755 index 0000000000..db3af6a833 --- /dev/null +++ b/contrib/difftool/git-difftool-helper @@ -0,0 +1,240 @@ +#!/bin/sh +# git-difftool-helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher. +# 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. +# +# Copyright (c) 2009 David Aguilar + +# Set GIT_DIFFTOOL_NO_PROMPT to bypass the per-file prompt. +should_prompt () { + ! test -n "$GIT_DIFFTOOL_NO_PROMPT" +} + +# Should we keep the backup .orig file? +keep_backup_mode="$(git config --bool merge.keepBackup || echo true)" +keep_backup () { + test "$keep_backup_mode" = "true" +} + +# This function manages the backup .orig file. +# A backup $MERGED.orig file is created if changes are detected. +cleanup_temp_files () { + if test -n "$MERGED"; then + if keep_backup && test "$MERGED" -nt "$BACKUP"; then + test -f "$BACKUP" && mv -- "$BACKUP" "$MERGED.orig" + else + rm -f -- "$BACKUP" + fi + fi +} + +# This is called when users Ctrl-C out of git-difftool-helper +sigint_handler () { + cleanup_temp_files + exit 1 +} + +# This function prepares temporary files and launches the appropriate +# merge tool. +launch_merge_tool () { + # Merged is the filename as it appears in the work tree + # Local is the contents of a/filename + # Remote is the contents of b/filename + # Custom merge tool commands might use $BASE so we provide it + MERGED="$1" + LOCAL="$2" + REMOTE="$3" + BASE="$1" + ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" + BACKUP="$MERGED.BACKUP.$ext" + + # Create and ensure that we clean up $BACKUP + test -f "$MERGED" && cp -- "$MERGED" "$BACKUP" + trap sigint_handler INT + + # $LOCAL and $REMOTE are temporary files so prompt + # the user with the real $MERGED name before launching $merge_tool. + if should_prompt; then + printf "\nViewing: '$MERGED'\n" + printf "Hit return to launch '%s': " "$merge_tool" + read ans + fi + + # Run the appropriate merge tool command + case "$merge_tool" in + kdiff3) + basename=$(basename "$MERGED") + "$merge_tool_path" --auto \ + --L1 "$basename (A)" \ + --L2 "$basename (B)" \ + -o "$MERGED" "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1 + ;; + + kompare) + "$merge_tool_path" "$LOCAL" "$REMOTE" + ;; + + tkdiff) + "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE" + ;; + + meld) + "$merge_tool_path" "$LOCAL" "$REMOTE" + ;; + + vimdiff) + "$merge_tool_path" -c "wincmd l" "$LOCAL" "$REMOTE" + ;; + + gvimdiff) + "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$REMOTE" + ;; + + xxdiff) + "$merge_tool_path" \ + -X \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$MERGED" \ + "$LOCAL" "$REMOTE" + ;; + + opendiff) + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + -merge "$MERGED" | cat + ;; + + ecmerge) + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + --default --mode=merge2 --to="$MERGED" + ;; + + emerge) + "$merge_tool_path" -f emerge-files-command \ + "$LOCAL" "$REMOTE" "$(basename "$MERGED")" + ;; + + *) + if test -n "$merge_tool_cmd"; then + ( eval $merge_tool_cmd ) + fi + ;; + esac + + cleanup_temp_files +} + +# Verifies that mergetool.<tool>.cmd exists +valid_custom_tool() { + merge_tool_cmd="$(git config mergetool.$1.cmd)" + test -n "$merge_tool_cmd" +} + +# Verifies that the chosen merge tool is properly setup. +# Built-in merge tools are always valid. +valid_tool() { + case "$1" in + kdiff3 | kompare | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) + ;; # happy + *) + if ! valid_custom_tool "$1" + then + return 1 + fi + ;; + esac +} + +# Sets up the merge_tool_path variable. +# This handles the mergetool.<tool>.path configuration. +init_merge_tool_path() { + merge_tool_path=$(git config mergetool."$1".path) + if test -z "$merge_tool_path"; then + case "$1" in + emerge) + merge_tool_path=emacs + ;; + *) + merge_tool_path="$1" + ;; + esac + fi +} + +# Allow the GIT_MERGE_TOOL variable to provide a default value +test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" + +# If not merge tool was specified then use the merge.tool +# configuration variable. If that's invalid then reset merge_tool. +if test -z "$merge_tool"; then + merge_tool=$(git config merge.tool) + if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then + echo >&2 "git config option merge.tool set to unknown tool: $merge_tool" + echo >&2 "Resetting to default..." + unset merge_tool + fi +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 + # 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 + if echo "${VISUAL:-$EDITOR}" | grep 'emacs' > /dev/null 2>&1; then + # $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 + echo "merge tool candidates: $merge_tool_candidates" + + # Loop over each candidate and stop when a valid merge tool is found. + for i in $merge_tool_candidates + do + init_merge_tool_path $i + if type "$merge_tool_path" > /dev/null 2>&1; then + merge_tool=$i + break + fi + done + + if test -z "$merge_tool" ; then + echo "No known merge resolution program available." + exit 1 + fi + +else + # A merge tool has been set, so verify that it's valid. + if ! valid_tool "$merge_tool"; then + echo >&2 "Unknown merge tool $merge_tool" + exit 1 + fi + + init_merge_tool_path "$merge_tool" + + if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then + echo "The merge tool $merge_tool is not available as '$merge_tool_path'" + exit 1 + fi +fi + + +# Launch the merge tool on each path provided by 'git diff' +while test $# -gt 6 +do + launch_merge_tool "$1" "$2" "$5" + shift 7 +done diff --git a/contrib/difftool/git-difftool.txt b/contrib/difftool/git-difftool.txt new file mode 100644 index 0000000000..6e2610cda6 --- /dev/null +++ b/contrib/difftool/git-difftool.txt @@ -0,0 +1,105 @@ +git-difftool(1) +=============== + +NAME +---- +git-difftool - compare changes using common merge tools + +SYNOPSIS +-------- +'git difftool' [--tool=<tool>] [--no-prompt] ['git diff' options] + +DESCRIPTION +----------- +'git-difftool' is a git command that allows you to compare and edit files +between revisions using common merge tools. At its most basic level, +'git-difftool' does what 'git-mergetool' does but its use is for non-merge +situations such as when preparing commits or comparing changes against +the index. + +'git difftool' is a frontend to 'git diff' and accepts the same +arguments and options. + +See linkgit:git-diff[1] for the full list of supported options. + +OPTIONS +------- +-t <tool>:: +--tool=<tool>:: + Use the merge resolution program specified by <tool>. + Valid merge tools are: + 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 +configuration variable `merge.tool` is not set, 'git difftool' +will pick a suitable default. ++ +You can explicitly provide a full path to the tool by setting the +configuration variable `mergetool.<tool>.path`. For example, you +can configure the absolute path to kdiff3 by setting +`mergetool.kdiff3.path`. Otherwise, 'git-difftool' assumes the +tool is available in PATH. ++ +Instead of running one of the known merge tool programs, +'git-difftool' can be customized to run an alternative program +by specifying the command line to invoke in a configuration +variable `mergetool.<tool>.cmd`. ++ +When 'git-difftool' is invoked with this tool (either through the +`-t` or `--tool` option or the `merge.tool` configuration variable) +the configured command line will be invoked with the following +variables available: `$LOCAL` is set to the name of the temporary +file containing the contents of the diff pre-image and `$REMOTE` +is set to the name of the temporary file containing the contents +of the diff post-image. `$BASE` is provided for compatibility +with custom merge tool commands and has the same value as `$LOCAL`. + +--no-prompt:: + Do not prompt before launching a diff tool. + +CONFIG VARIABLES +---------------- +merge.tool:: + The default merge tool to use. ++ +See the `--tool=<tool>` option above for more details. + +merge.keepBackup:: + The original, unedited file content can be saved to a file with + a `.orig` extension. Defaults to `true` (i.e. keep the backup files). + +mergetool.<tool>.path:: + Override the path for the given tool. This is useful in case + your tool is not in the PATH. + +mergetool.<tool>.cmd:: + Specify the command to invoke the specified merge tool. ++ +See the `--tool=<tool>` option above for more details. + + +SEE ALSO +-------- +linkgit:git-diff[1]:: + Show changes between commits, commit and working tree, etc + +linkgit:git-mergetool[1]:: + Run merge conflict resolution tools to resolve merge conflicts + +linkgit:git-config[1]:: + Get and set repository or global options + + +AUTHOR +------ +Written by David Aguilar <davvid@gmail.com>. + +Documentation +-------------- +Documentation by David Aguilar and the git-list <git@vger.kernel.org>. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/contrib/emacs/Makefile b/contrib/emacs/Makefile index a48540a92b..24d9312941 100644 --- a/contrib/emacs/Makefile +++ b/contrib/emacs/Makefile @@ -2,7 +2,7 @@ EMACS = emacs -ELC = git.elc vc-git.elc git-blame.elc +ELC = git.elc git-blame.elc INSTALL ?= install INSTALL_ELC = $(INSTALL) -m 644 prefix ?= $(HOME) diff --git a/contrib/emacs/README b/contrib/emacs/README new file mode 100644 index 0000000000..82368bdbff --- /dev/null +++ b/contrib/emacs/README @@ -0,0 +1,39 @@ +This directory contains various modules for Emacs support. + +To make the modules available to Emacs, you should add this directory +to your load-path, and then require the modules you want. This can be +done by adding to your .emacs something like this: + + (add-to-list 'load-path ".../git/contrib/emacs") + (require 'git) + (require 'git-blame) + + +The following modules are available: + +* git.el: + + Status manager that displays the state of all the files of the + project, and provides easy access to the most frequently used git + commands. The user interface is as far as possible compatible with + the pcl-cvs mode. It can be started with `M-x git-status'. + +* git-blame.el: + + Emacs implementation of incremental git-blame. When you turn it on + while viewing a file, the editor buffer will be updated by setting + the background of individual lines to a color that reflects which + commit it comes from. And when you move around the buffer, a + one-line summary will be shown in the echo area. + +* vc-git.el: + + This file used to contain the VC-mode backend for git, but it is no + longer distributed with git. It is now maintained as part of Emacs + and included in standard Emacs distributions starting from version + 22.2. + + If you have an earlier Emacs version, upgrading to Emacs 22 is + recommended, since the VC mode in older Emacs is not generic enough + to be able to support git in a reasonable manner, and no attempt has + been made to backport vc-git.el. diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el index 09e8bae3a4..eace9c18eb 100644 --- a/contrib/emacs/git.el +++ b/contrib/emacs/git.el @@ -1,6 +1,6 @@ ;;; git.el --- A user interface for git -;; Copyright (C) 2005, 2006, 2007 Alexandre Julliard <julliard@winehq.org> +;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Alexandre Julliard <julliard@winehq.org> ;; Version: 1.0 @@ -34,15 +34,21 @@ ;; To start: `M-x git-status' ;; ;; TODO -;; - portability to XEmacs ;; - diff against other branch ;; - renaming files from the status buffer ;; - creating tags ;; - fetch/pull -;; - switching branches ;; - revlist browser ;; - git-show-branch browser -;; - menus +;; + +;;; Compatibility: +;; +;; This file works on GNU Emacs 21 or later. It may work on older +;; versions but this is not guaranteed. +;; +;; It may work on XEmacs 21, provided that you first install the ewoc +;; and log-edit packages. ;; (eval-when-compile (require 'cl)) @@ -222,7 +228,7 @@ the process output as a string, or nil if the git command failed." (with-current-buffer buffer (cd dir) (apply #'call-process-region start end program - nil (list output-buffer nil) nil args)))) + nil (list output-buffer t) nil args)))) (defun git-run-command-buffer (buffer-name &rest args) "Run a git command, sending the output to a buffer named BUFFER-NAME." @@ -239,13 +245,15 @@ the process output as a string, or nil if the git command failed." (defun git-run-command-region (buffer start end env &rest args) "Run a git command with specified buffer region as input." - (unless (eq 0 (if env - (git-run-process-region - buffer start end "env" - (append (git-get-env-strings env) (list "git") args)) + (with-temp-buffer + (if (eq 0 (if env (git-run-process-region - buffer start end "git" args))) - (error "Failed to run \"git %s\":\n%s" (mapconcat (lambda (x) x) args " ") (buffer-string)))) + buffer start end "env" + (append (git-get-env-strings env) (list "git") args)) + (git-run-process-region buffer start end "git" args))) + (buffer-string) + (display-message-or-buffer (current-buffer)) + nil))) (defun git-run-hook (hook env &rest args) "Run a git hook and display its output if any." @@ -397,6 +405,17 @@ the process output as a string, or nil if the git command failed." (unless newval (push "-d" args)) (apply 'git-call-process-display-error "update-ref" args))) +(defun git-for-each-ref (&rest specs) + "Return a list of refs using git-for-each-ref. +Each entry is a cons of (SHORT-NAME . FULL-NAME)." + (let (refs) + (with-temp-buffer + (apply #'git-call-process t "for-each-ref" "--format=%(refname)" specs) + (goto-char (point-min)) + (while (re-search-forward "^[^/\n]+/[^/\n]+/\\(.+\\)$" nil t) + (push (cons (match-string 1) (match-string 0)) refs))) + (nreverse refs))) + (defun git-read-tree (tree &optional index-file) "Read a tree into the index file." (let ((process-environment @@ -447,18 +466,16 @@ the process output as a string, or nil if the git command failed." (setq coding-system-for-write buffer-file-coding-system)) (let ((commit (git-get-string-sha1 - (with-output-to-string - (with-current-buffer standard-output - (let ((env `(("GIT_AUTHOR_NAME" . ,author-name) - ("GIT_AUTHOR_EMAIL" . ,author-email) - ("GIT_COMMITTER_NAME" . ,(git-get-committer-name)) - ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email))))) - (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env)) - (apply #'git-run-command-region - buffer log-start log-end env - "commit-tree" tree (nreverse args)))))))) - (and (git-update-ref "HEAD" commit head subject) - commit)))) + (let ((env `(("GIT_AUTHOR_NAME" . ,author-name) + ("GIT_AUTHOR_EMAIL" . ,author-email) + ("GIT_COMMITTER_NAME" . ,(git-get-committer-name)) + ("GIT_COMMITTER_EMAIL" . ,(git-get-committer-email))))) + (when author-date (push `("GIT_AUTHOR_DATE" . ,author-date) env)) + (apply #'git-run-command-region + buffer log-start log-end env + "commit-tree" tree (nreverse args)))))) + (when commit (git-update-ref "HEAD" commit head subject)) + commit))) (defun git-empty-db-p () "Check if the git db is empty (no commit done yet)." @@ -513,9 +530,9 @@ the process output as a string, or nil if the git command failed." (git-fileinfo->needs-refresh info) t))) (defun git-status-filenames-map (status func files &rest args) - "Apply FUNC to the status files names in the FILES list." + "Apply FUNC to the status files names in the FILES list. +The list must be sorted." (when files - (setq files (sort files #'string-lessp)) (let ((file (pop files)) (node (ewoc-nth status 0))) (while (and file node) @@ -528,7 +545,7 @@ the process output as a string, or nil if the git command failed." (setq file (pop files)))))))) (defun git-set-filenames-state (status files state) - "Set the state of a list of named files." + "Set the state of a list of named files. The list must be sorted" (when files (git-status-filenames-map status #'git-set-fileinfo-state files state) (unless state ;; delete files whose state has been set to nil @@ -562,29 +579,29 @@ the process output as a string, or nil if the git command failed." (let* ((old-type (lsh (or old-perm 0) -9)) (new-type (lsh (or new-perm 0) -9)) (str (case new-type - (?\100 ;; file + (64 ;; file (case old-type - (?\100 nil) - (?\120 " (type change symlink -> file)") - (?\160 " (type change subproject -> file)"))) - (?\120 ;; symlink + (64 nil) + (80 " (type change symlink -> file)") + (112 " (type change subproject -> file)"))) + (80 ;; symlink (case old-type - (?\100 " (type change file -> symlink)") - (?\160 " (type change subproject -> symlink)") + (64 " (type change file -> symlink)") + (112 " (type change subproject -> symlink)") (t " (symlink)"))) - (?\160 ;; subproject + (112 ;; subproject (case old-type - (?\100 " (type change file -> subproject)") - (?\120 " (type change symlink -> subproject)") + (64 " (type change file -> subproject)") + (80 " (type change symlink -> subproject)") (t " (subproject)"))) - (?\110 nil) ;; directory (internal, not a real git state) - (?\000 ;; deleted or unknown + (72 nil) ;; directory (internal, not a real git state) + (0 ;; deleted or unknown (case old-type - (?\120 " (symlink)") - (?\160 " (subproject)"))) + (80 " (symlink)") + (112 " (subproject)"))) (t (format " (unknown type %o)" new-type))))) (cond (str (propertize str 'face 'git-status-face)) - ((eq new-type ?\110) "/") + ((eq new-type 72) "/") (t "")))) (defun git-rename-as-string (info) @@ -733,6 +750,7 @@ Return the list of files that haven't been handled." (let (unmerged-files) (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t) (push (match-string 1) unmerged-files)) + (setq unmerged-files (nreverse unmerged-files)) ;; assume it is sorted already (git-set-filenames-state status unmerged-files 'unmerged)))) (defun git-get-exclude-files () @@ -753,17 +771,18 @@ Return the list of files that haven't been handled." (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files))))) (defun git-update-status-files (&optional files mark-files) - "Update the status of FILES from the index." + "Update the status of FILES from the index. +The FILES list must be sorted." (unless git-status (error "Not in git-status buffer.")) ;; set the needs-update flag on existing files - (if (setq files (sort files #'string-lessp)) + (if files (git-status-filenames-map git-status (lambda (info) (setf (git-fileinfo->needs-update info) t)) files) (ewoc-map (lambda (info) (setf (git-fileinfo->needs-update info) t) nil) git-status) (git-call-process nil "update-index" "--refresh") (when git-show-uptodate (git-run-ls-files-cached git-status nil 'uptodate))) - (let* ((remaining-files + (let ((remaining-files (if (git-empty-db-p) ; we need some special handling for an empty db (git-run-ls-files-cached git-status files 'added) (git-run-diff-index git-status files)))) @@ -808,13 +827,13 @@ Return the list of files that haven't been handled." (list (ewoc-data (ewoc-locate git-status))))) (defun git-marked-files-state (&rest states) - "Return marked files that are in the specified states." + "Return a sorted list of marked files that are in the specified states." (let ((files (git-marked-files)) result) (dolist (info files) (when (memq (git-fileinfo->state info) states) (push info result))) - result)) + (nreverse result))) (defun git-refresh-files () "Refresh all files that need it and clear the needs-refresh flag." @@ -1049,7 +1068,9 @@ Return the list of files that haven't been handled." (unless files (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files)) (if (yes-or-no-p - (format "Remove %d file%s? " (length files) (if (> (length files) 1) "s" ""))) + (if (cdr files) + (format "Remove %d files? " (length files)) + (format "Remove %s? " (car files)))) (progn (dolist (name files) (ignore-errors @@ -1068,7 +1089,9 @@ Return the list of files that haven't been handled." added modified) (when (and files (yes-or-no-p - (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" "")))) + (if (cdr files) + (format "Revert %d files? " (length files)) + (format "Revert %s? " (git-fileinfo->name (car files)))))) (dolist (info files) (case (git-fileinfo->state info) ('added (push (git-fileinfo->name info) added)) @@ -1084,13 +1107,14 @@ Return the list of files that haven't been handled." (or (not added) (apply 'git-call-process-display-error "update-index" "--force-remove" "--" added)) (or (not modified) - (apply 'git-call-process-display-error "checkout" "HEAD" modified))))) - (git-update-status-files (append added modified)) + (apply 'git-call-process-display-error "checkout" "HEAD" modified)))) + (names (git-get-filenames files))) + (git-update-status-files names) (when ok (dolist (file modified) (let ((buffer (get-file-buffer file))) (when buffer (with-current-buffer buffer (revert-buffer t t t))))) - (git-success-message "Reverted" (git-get-filenames files))))))) + (git-success-message "Reverted" names)))))) (defun git-resolve-file () "Resolve conflicts in marked file(s)." @@ -1320,6 +1344,7 @@ Return the list of files that haven't been handled." (log-edit-diff-function . git-log-edit-diff)) buffer) (log-edit 'git-do-commit nil 'git-log-edit-files buffer)) (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords)) + (setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$")) (setq buffer-file-coding-system coding-system) (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))) @@ -1347,14 +1372,44 @@ Return the list of files that haven't been handled." (mapconcat #'identity msg "\n")))) (defun git-get-commit-files (commit) - "Retrieve the list of files modified by COMMIT." + "Retrieve a sorted list of files modified by COMMIT." (let (files) (with-temp-buffer (git-call-process t "diff-tree" "-m" "-r" "-z" "--name-only" "--no-commit-id" "--root" commit) (goto-char (point-min)) (while (re-search-forward "\\([^\0]*\\)\0" nil t 1) (push (match-string 1) files))) - files)) + (sort files #'string-lessp))) + +(defun git-read-commit-name (prompt &optional default) + "Ask for a commit name, with completion for local branch, remote branch and tag." + (completing-read prompt + (list* "HEAD" "ORIG_HEAD" "FETCH_HEAD" (mapcar #'car (git-for-each-ref))) + nil nil nil nil default)) + +(defun git-checkout (branch &optional merge) + "Checkout a branch, tag, or any commit. +Use a prefix arg if git should merge while checking out." + (interactive + (list (git-read-commit-name "Checkout: ") + current-prefix-arg)) + (unless git-status (error "Not in git-status buffer.")) + (let ((args (list branch "--"))) + (when merge (push "-m" args)) + (when (apply #'git-call-process-display-error "checkout" args) + (git-update-status-files)))) + +(defun git-branch (branch) + "Create a branch from the current HEAD and switch to it." + (interactive (list (git-read-commit-name "Branch: "))) + (unless git-status (error "Not in git-status buffer.")) + (if (git-rev-parse (concat "refs/heads/" branch)) + (if (yes-or-no-p (format "Branch %s already exists, replace it? " branch)) + (and (git-call-process-display-error "branch" "-f" branch) + (git-call-process-display-error "checkout" branch)) + (message "Canceled.")) + (git-call-process-display-error "checkout" "-b" branch)) + (git-refresh-ewoc-hf git-status)) (defun git-amend-commit () "Undo the last commit on HEAD, and set things up to commit an @@ -1372,6 +1427,44 @@ amended version of it." (git-setup-commit-buffer commit) (git-commit-file)))) +(defun git-cherry-pick-commit (arg) + "Cherry-pick a commit." + (interactive (list (git-read-commit-name "Cherry-pick commit: "))) + (unless git-status (error "Not in git-status buffer.")) + (let ((commit (git-rev-parse (concat arg "^0")))) + (unless commit (error "Not a valid commit '%s'." arg)) + (when (git-rev-parse (concat commit "^2")) + (error "Cannot cherry-pick a merge commit.")) + (let ((files (git-get-commit-files commit)) + (ok (git-call-process-display-error "cherry-pick" "-n" commit))) + (git-update-status-files files ok) + (with-current-buffer (git-setup-commit-buffer commit) + (goto-char (point-min)) + (if (re-search-forward "^\n*Signed-off-by:" nil t 1) + (goto-char (match-beginning 0)) + (goto-char (point-max))) + (insert "(cherry picked from commit " commit ")\n")) + (when ok (git-commit-file))))) + +(defun git-revert-commit (arg) + "Revert a commit." + (interactive (list (git-read-commit-name "Revert commit: "))) + (unless git-status (error "Not in git-status buffer.")) + (let ((commit (git-rev-parse (concat arg "^0")))) + (unless commit (error "Not a valid commit '%s'." arg)) + (when (git-rev-parse (concat commit "^2")) + (error "Cannot revert a merge commit.")) + (let ((files (git-get-commit-files commit)) + (subject (git-get-commit-description commit)) + (ok (git-call-process-display-error "revert" "-n" commit))) + (git-update-status-files files ok) + (when (string-match "^[0-9a-f]+ - \\(.*\\)$" subject) + (setq subject (match-string 1 subject))) + (git-setup-log-buffer (get-buffer-create "*git-commit*") + (git-get-merge-heads) nil nil (format "Revert \"%s\"" subject) nil + (format "This reverts commit %s.\n" commit)) + (when ok (git-commit-file))))) + (defun git-find-file () "Visit the current file in its own buffer." (interactive) @@ -1471,6 +1564,10 @@ amended version of it." (define-key map "\M-\C-?" 'git-unmark-all) ; the commit submap (define-key commit-map "\C-a" 'git-amend-commit) + (define-key commit-map "\C-b" 'git-branch) + (define-key commit-map "\C-o" 'git-checkout) + (define-key commit-map "\C-p" 'git-cherry-pick-commit) + (define-key commit-map "\C-v" 'git-revert-commit) ; the diff submap (define-key diff-map "b" 'git-diff-file-base) (define-key diff-map "c" 'git-diff-file-combined) @@ -1491,6 +1588,10 @@ amended version of it." `("Git" ["Refresh" git-refresh-status t] ["Commit" git-commit-file t] + ["Checkout..." git-checkout t] + ["New Branch..." git-branch t] + ["Cherry-pick Commit..." git-cherry-pick-commit t] + ["Revert Commit..." git-revert-commit t] ("Merge" ["Next Unmerged File" git-next-unmerged-file t] ["Prev Unmerged File" git-prev-unmerged-file t] diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el deleted file mode 100644 index b8f6be5c0a..0000000000 --- a/contrib/emacs/vc-git.el +++ /dev/null @@ -1,216 +0,0 @@ -;;; vc-git.el --- VC backend for the git version control system - -;; Copyright (C) 2006 Alexandre Julliard - -;; This program is free software; you can redistribute it and/or -;; modify it under the terms of the GNU General Public License as -;; published by the Free Software Foundation; either version 2 of -;; the License, or (at your option) any later version. -;; -;; This program is distributed in the hope that it will be -;; useful, but WITHOUT ANY WARRANTY; without even the implied -;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR -;; PURPOSE. See the GNU General Public License for more details. -;; -;; You should have received a copy of the GNU General Public -;; License along with this program; if not, write to the Free -;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, -;; MA 02111-1307 USA - -;;; Commentary: - -;; This file contains a VC backend for the git version control -;; system. -;; -;; To install: put this file on the load-path and add GIT to the list -;; of supported backends in `vc-handled-backends'; the following line, -;; placed in your ~/.emacs, will accomplish this: -;; -;; (add-to-list 'vc-handled-backends 'GIT) -;; -;; TODO -;; - changelog generation -;; - working with revisions other than HEAD -;; - -(eval-when-compile (require 'cl)) - -(defvar git-commits-coding-system 'utf-8 - "Default coding system for git commits.") - -(defun vc-git--run-command-string (file &rest args) - "Run a git command on FILE and return its output as string." - (let* ((ok t) - (str (with-output-to-string - (with-current-buffer standard-output - (unless (eq 0 (apply #'call-process "git" nil '(t nil) nil - (append args (list (file-relative-name file))))) - (setq ok nil)))))) - (and ok str))) - -(defun vc-git--run-command (file &rest args) - "Run a git command on FILE, discarding any output." - (let ((name (file-relative-name file))) - (eq 0 (apply #'call-process "git" nil (get-buffer "*Messages") nil (append args (list name)))))) - -(defun vc-git-registered (file) - "Check whether FILE is registered with git." - (with-temp-buffer - (let* ((dir (file-name-directory file)) - (name (file-relative-name file dir))) - (and (ignore-errors - (when dir (cd dir)) - (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) - (let ((str (buffer-string))) - (and (> (length str) (length name)) - (string= (substring str 0 (1+ (length name))) (concat name "\0")))))))) - -(defun vc-git-state (file) - "git-specific version of `vc-state'." - (let ((diff (vc-git--run-command-string file "diff-index" "-z" "HEAD" "--"))) - (if (and diff (string-match ":[0-7]\\{6\\} [0-7]\\{6\\} [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} [ADMU]\0[^\0]+\0" diff)) - 'edited - 'up-to-date))) - -(defun vc-git-workfile-version (file) - "git-specific version of `vc-workfile-version'." - (let ((str (with-output-to-string - (with-current-buffer standard-output - (call-process "git" nil '(t nil) nil "symbolic-ref" "HEAD"))))) - (if (string-match "^\\(refs/heads/\\)?\\(.+\\)$" str) - (match-string 2 str) - str))) - -(defun vc-git-symbolic-commit (commit) - "Translate COMMIT string into symbolic form. -Returns nil if not possible." - (and commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "name-rev" - "--name-only" "--tags" - commit)) - (goto-char (point-min)) - (= (forward-line 2) 1) - (bolp) - (buffer-substring-no-properties (point-min) (1- (point-max))))))) - -(defun vc-git-previous-version (file rev) - "git-specific version of `vc-previous-version'." - (let ((default-directory (file-name-directory (expand-file-name file))) - (file (file-name-nondirectory file))) - (vc-git-symbolic-commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "-2" rev "--" file)) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (not (bobp)) - (buffer-substring-no-properties - (point) - (1- (point-max)))))))) - -(defun vc-git-next-version (file rev) - "git-specific version of `vc-next-version'." - (let* ((default-directory (file-name-directory - (expand-file-name file))) - (file (file-name-nondirectory file)) - (current-rev - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "-1" rev "--" file)) - (goto-char (point-max)) - (bolp) - (zerop (forward-line -1)) - (bobp) - (buffer-substring-no-properties - (point) - (1- (point-max))))))) - (and current-rev - (vc-git-symbolic-commit - (with-temp-buffer - (and - (zerop - (call-process "git" nil '(t nil) nil "rev-list" - "HEAD" "--" file)) - (goto-char (point-min)) - (search-forward current-rev nil t) - (zerop (forward-line -1)) - (buffer-substring-no-properties - (point) - (progn (forward-line 1) (1- (point)))))))))) - -(defun vc-git-revert (file &optional contents-done) - "Revert FILE to the version stored in the git repository." - (if contents-done - (vc-git--run-command file "update-index" "--") - (vc-git--run-command file "checkout" "HEAD"))) - -(defun vc-git-checkout-model (file) - 'implicit) - -(defun vc-git-workfile-unchanged-p (file) - (let ((sha1 (vc-git--run-command-string file "hash-object" "--")) - (head (vc-git--run-command-string file "ls-tree" "-z" "HEAD" "--"))) - (and head - (string-match "[0-7]\\{6\\} blob \\([0-9a-f]\\{40\\}\\)\t[^\0]+\0" head) - (string= (car (split-string sha1 "\n")) (match-string 1 head))))) - -(defun vc-git-register (file &optional rev comment) - "Register FILE into the git version-control system." - (vc-git--run-command file "update-index" "--add" "--")) - -(defun vc-git-print-log (file &optional buffer) - (let ((name (file-relative-name file)) - (coding-system-for-read git-commits-coding-system)) - (vc-do-command buffer 'async "git" name "rev-list" "--pretty" "HEAD" "--"))) - -(defun vc-git-diff (file &optional rev1 rev2 buffer) - (let ((name (file-relative-name file)) - (buf (or buffer "*vc-diff*"))) - (if (and rev1 rev2) - (vc-do-command buf 0 "git" name "diff-tree" "-p" rev1 rev2 "--") - (vc-do-command buf 0 "git" name "diff-index" "-p" (or rev1 "HEAD") "--")) - ; git-diff-index doesn't set exit status like diff does - (if (vc-git-workfile-unchanged-p file) 0 1))) - -(defun vc-git-checkin (file rev comment) - (let ((coding-system-for-write git-commits-coding-system)) - (vc-git--run-command file "commit" "-m" comment "--only" "--"))) - -(defun vc-git-checkout (file &optional editable rev destfile) - (if destfile - (let ((fullname (substring - (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--") - 0 -1)) - (coding-system-for-read 'no-conversion) - (coding-system-for-write 'no-conversion)) - (with-temp-file destfile - (eq 0 (call-process "git" nil t nil "cat-file" "blob" - (concat (or rev "HEAD") ":" fullname))))) - (vc-git--run-command file "checkout" (or rev "HEAD")))) - -(defun vc-git-annotate-command (file buf &optional rev) - ; FIXME: rev is ignored - (let ((name (file-relative-name file))) - (call-process "git" nil buf nil "blame" name))) - -(defun vc-git-annotate-time () - (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t) - (vc-annotate-convert-time - (apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7)))))) - -;; Not really useful since we can't do anything with the revision yet -;;(defun vc-annotate-extract-revision-at-line () -;; (save-excursion -;; (move-beginning-of-line 1) -;; (and (looking-at "[0-9a-f]+") -;; (buffer-substring (match-beginning 0) (match-end 0))))) - -(provide 'vc-git) diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl index a13bb6afec..4576c4a862 100755 --- a/contrib/examples/git-svnimport.perl +++ b/contrib/examples/git-svnimport.perl @@ -287,9 +287,9 @@ my $last_rev = ""; my $last_branch; my $current_rev = $opt_s || 1; unless(-d $git_dir) { - system("git-init"); + system("git init"); die "Cannot init the GIT db at $git_tree: $?\n" if $?; - system("git-read-tree"); + system("git read-tree"); die "Cannot init an empty tree: $?\n" if $?; $last_branch = $opt_o; @@ -303,7 +303,7 @@ unless(-d $git_dir) { -f "$git_dir/svn2git" or die "'$git_dir/svn2git' does not exist.\n". "You need that file for incremental imports.\n"; - open(F, "git-symbolic-ref HEAD |") or + open(F, "git symbolic-ref HEAD |") or die "Cannot run git-symbolic-ref: $!\n"; chomp ($last_branch = <F>); $last_branch = basename($last_branch); @@ -331,7 +331,7 @@ EOM "$git_dir/refs/heads/$opt_o") == 0; # populate index - system('git-read-tree', $last_rev); + system('git', 'read-tree', $last_rev); die "read-tree failed: $?\n" if $?; # Get the last import timestamps @@ -399,7 +399,7 @@ sub get_file($$$) { my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { - exec("git-hash-object", "-w", $name) + exec("git", "hash-object", "-w", $name) or die "Cannot create object: $!\n"; } my $sha = <$F>; @@ -423,7 +423,7 @@ sub get_ignore($$$$$) { my $pid = open(my $F, '-|'); die $! unless defined $pid; if (!$pid) { - exec("git-hash-object", "-w", $name) + exec("git", "hash-object", "-w", $name) or die "Cannot create object: $!\n"; } my $sha = <$F>; @@ -547,7 +547,7 @@ sub copy_path($$$$$$$$) { my $pid = open my $f,'-|'; die $! unless defined $pid; if (!$pid) { - exec("git-ls-tree","-r","-z",$gitrev,$srcpath) + exec("git","ls-tree","-r","-z",$gitrev,$srcpath) or die $!; } local $/ = "\0"; @@ -634,7 +634,7 @@ sub commit { my $rev; if($revision > $opt_s and defined $parent) { - open(H,'-|',"git-rev-parse","--verify",$parent); + open(H,'-|',"git","rev-parse","--verify",$parent); $rev = <H>; close(H) or do { print STDERR "$revision: cannot find commit '$parent'!\n"; @@ -671,7 +671,7 @@ sub commit { unlink($git_index); } elsif ($rev ne $last_rev) { print "Switching from $last_rev to $rev ($branch)\n" if $opt_v; - system("git-read-tree", $rev); + system("git", "read-tree", $rev); die "read-tree failed for $rev: $?\n" if $?; $last_rev = $rev; } @@ -740,7 +740,7 @@ sub commit { my $pid = open my $F, "-|"; die "$!" unless defined $pid; if (!$pid) { - exec("git-ls-files", "-z", @o1) or die $!; + exec("git", "ls-files", "-z", @o1) or die $!; } @o1 = (); local $/ = "\0"; @@ -758,7 +758,7 @@ sub commit { @o2 = @o1; @o1 = (); } - system("git-update-index","--force-remove","--",@o2); + system("git","update-index","--force-remove","--",@o2); die "Cannot remove files: $?\n" if $?; } } @@ -770,7 +770,7 @@ sub commit { @n2 = @new; @new = (); } - system("git-update-index","--add", + system("git","update-index","--add", (map { ('--cacheinfo', @$_) } @n2)); die "Cannot add files: $?\n" if $?; } @@ -778,7 +778,7 @@ sub commit { my $pid = open(C,"-|"); die "Cannot fork: $!" unless defined $pid; unless($pid) { - exec("git-write-tree"); + exec("git","write-tree"); die "Cannot exec git-write-tree: $!\n"; } chomp(my $tree = <C>); @@ -830,7 +830,7 @@ sub commit { "GIT_COMMITTER_NAME=$committer_name", "GIT_COMMITTER_EMAIL=$committer_email", "GIT_COMMITTER_DATE=".strftime("+0000 %Y-%m-%d %H:%M:%S",gmtime($date)), - "git-commit-tree", $tree,@par); + "git", "commit-tree", $tree,@par); die "Cannot exec git-commit-tree: $!\n"; } $pw->writer(); @@ -874,7 +874,7 @@ sub commit { $dest =~ tr/_/\./ if $opt_u; - system('git-tag', '-f', $dest, $cid) == 0 + system('git', 'tag', '-f', $dest, $cid) == 0 or die "Cannot create tag $dest: $!\n"; print "Created tag '$dest' on '$branch'\n" if $opt_v; @@ -937,7 +937,7 @@ while ($to_rev < $opt_l) { my $pid = fork(); die "Fork: $!\n" unless defined $pid; unless($pid) { - exec("git-repack", "-d") + exec("git", "repack", "-d") or die "Cannot repack: $!\n"; } waitpid($pid, 0); @@ -958,7 +958,7 @@ if($orig_branch) { system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") if $forward_master; unless ($opt_i) { - system('git-read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); + system('git', 'read-tree', '-m', '-u', 'SVN2GIT_HEAD', 'HEAD'); die "read-tree failed: $?\n" if $?; } } else { @@ -966,7 +966,7 @@ if($orig_branch) { print "DONE; creating $orig_branch branch\n" if $opt_v and (not defined $opt_l or $opt_l > 0); system("cp","$git_dir/refs/heads/$opt_o","$git_dir/refs/heads/master") unless -f "$git_dir/refs/heads/master"; - system('git-update-ref', 'HEAD', "$orig_branch"); + system('git', 'update-ref', 'HEAD', "$orig_branch"); unless ($opt_i) { system('git checkout'); die "checkout failed: $?\n" if $?; diff --git a/contrib/examples/git-svnimport.txt b/contrib/examples/git-svnimport.txt index 71aad8b45b..3bb871e42f 100644 --- a/contrib/examples/git-svnimport.txt +++ b/contrib/examples/git-svnimport.txt @@ -114,9 +114,9 @@ due to SVN memory leaks. (These have been worked around.) -R <repack_each_revs>:: Specify how often git repository should be repacked. + -The default value is 1000. git-svnimport will do import in chunks of 1000 -revisions, after each chunk git repository will be repacked. To disable -this behavior specify some big value here which is mote than number of +The default value is 1000. git-svnimport will do imports in chunks of 1000 +revisions, after each chunk the git repository will be repacked. To disable +this behavior specify some large value here which is greater than the number of revisions to import. -P <path_from_trunk>:: diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4 index a85a7b2a58..3832f60225 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -442,13 +442,14 @@ def p4ChangesForPaths(depotPaths, changeRange): output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange) for p in depotPaths])) - changes = [] + changes = {} for line in output: - changeNum = line.split(" ")[1] - changes.append(int(changeNum)) + changeNum = int(line.split(" ")[1]) + changes[changeNum] = True - changes.sort() - return changes + changelist = changes.keys() + changelist.sort() + return changelist class Command: def __init__(self): diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh new file mode 100755 index 0000000000..c364dda696 --- /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 diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 28a3c0e46e..60cbab65d3 100644 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -615,7 +615,9 @@ show_new_revisions() revspec=$oldrev..$newrev fi - git rev-parse --not --branches | grep -v $(git rev-parse $refname) | + other_branches=$(git for-each-ref --format='%(refname)' refs/heads/ | + grep -F -v $refname) + git rev-parse --not $other_branches | if [ -z "$custom_showrev" ] then git rev-list --pretty --stdin $revspec diff --git a/contrib/vim/README b/contrib/vim/README index c487346eba..fca1e17251 100644 --- a/contrib/vim/README +++ b/contrib/vim/README @@ -5,11 +5,13 @@ automatically. If you have an older version of vim, you can get the latest syntax files from the vim project: - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/git.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitcommit.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitconfig.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitrebase.vim - http://vim.svn.sourceforge.net/viewvc/vim/trunk/runtime/syntax/gitsendemail.vim + http://ftp.vim.org/pub/vim/runtime/syntax/git.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitcommit.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitconfig.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitrebase.vim + http://ftp.vim.org/pub/vim/runtime/syntax/gitsendemail.vim + +These files are also available via FTP at the same location. To install: @@ -5,25 +5,22 @@ */ #include "cache.h" -/* Just so that no insane platform contaminate namespace with these symbols */ -#undef SS -#undef AA -#undef DD -#undef GS - -#define SS GIT_SPACE -#define AA GIT_ALPHA -#define DD GIT_DIGIT -#define GS GIT_SPECIAL /* \0, *, ?, [, \\ */ +enum { + S = GIT_SPACE, + A = GIT_ALPHA, + D = GIT_DIGIT, + G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | * */ +}; unsigned char sane_ctype[256] = { - GS, 0, 0, 0, 0, 0, 0, 0, 0, SS, SS, 0, 0, SS, 0, 0, /* 0-15 */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-15 */ - SS, 0, 0, 0, 0, 0, 0, 0, 0, 0, GS, 0, 0, 0, 0, 0, /* 32-15 */ - DD, DD, DD, DD, DD, DD, DD, DD, DD, DD, 0, 0, 0, 0, 0, GS, /* 48-15 */ - 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 64-15 */ - AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, GS, GS, 0, 0, 0, /* 80-15 */ - 0, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, /* 96-15 */ - AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, 0, 0, 0, 0, 0, /* 112-15 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */ + S, 0, 0, 0, R, 0, 0, 0, R, R, G, R, 0, 0, R, 0, /* 32.. 47 */ + D, D, D, D, D, D, D, D, D, D, 0, 0, 0, 0, 0, G, /* 48.. 63 */ + 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, 0, R, 0, /* 80.. 95 */ + 0, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, 0, 0, 0, /* 112..127 */ /* Nothing in the 128.. range */ }; @@ -716,7 +716,7 @@ static int socksetup(char *listen_addr, int listen_port, int **socklist_p) gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); if (gai) - die("getaddrinfo() failed: %s\n", gai_strerror(gai)); + die("getaddrinfo() failed: %s", gai_strerror(gai)); for (ai = ai0; ai; ai = ai->ai_next) { int sockfd; @@ -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]; @@ -89,6 +89,11 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode) struct tm *tm; static char timebuf[200]; + if (mode == DATE_RAW) { + snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz); + return timebuf; + } + if (mode == DATE_RELATIVE) { unsigned long diff; struct timeval now; @@ -128,7 +133,25 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode) snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30); return timebuf; } - /* Else fall back on absolute format.. */ + /* Give years and months for 5 years or so */ + if (diff < 1825) { + unsigned long years = (diff + 183) / 365; + unsigned long months = (diff % 365 + 15) / 30; + int n; + n = snprintf(timebuf, sizeof(timebuf), "%lu year%s", + years, (years > 1 ? "s" : "")); + if (months) + snprintf(timebuf + n, sizeof(timebuf) - n, + ", %lu month%s ago", + months, (months > 1 ? "s" : "")); + else + snprintf(timebuf + n, sizeof(timebuf) - n, + " ago"); + return timebuf; + } + /* Otherwise, just years. Centuries is probably overkill. */ + snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365); + return timebuf; } if (mode == DATE_LOCAL) @@ -615,6 +638,8 @@ enum date_mode parse_date_format(const char *format) return DATE_LOCAL; else if (!strcmp(format, "default")) return DATE_NORMAL; + else if (!strcmp(format, "raw")) + return DATE_RAW; else die("unknown date format %s", format); } @@ -846,13 +871,15 @@ unsigned long approxidate(const char *date) int number = 0; struct tm tm, now; struct timeval tv; + time_t time_sec; char buffer[50]; if (parse_date(date, buffer, sizeof(buffer)) > 0) return strtoul(buffer, NULL, 10); gettimeofday(&tv, NULL); - localtime_r(&tv.tv_sec, &tm); + time_sec = tv.tv_sec; + localtime_r(&time_sec, &tm); now = tm; for (;;) { unsigned char c = *date; diff --git a/diff-lib.c b/diff-lib.c index ae96c64ca2..79d0606834 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -61,14 +61,12 @@ int run_diff_files(struct rev_info *revs, unsigned int option) int silent_on_removed = option & DIFF_SILENT_ON_REMOVED; unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED) ? CE_MATCH_RACY_IS_DIRTY : 0); - char symcache[PATH_MAX]; diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/"); if (diff_unmerged_stage < 0) diff_unmerged_stage = 2; entries = active_nr; - symcache[0] = '\0'; for (i = 0; i < entries; i++) { struct stat st; unsigned int oldmode, newmode; @@ -198,11 +196,6 @@ int run_diff_files(struct rev_info *revs, unsigned int option) * diff-index */ -struct oneway_unpack_data { - struct rev_info *revs; - char symcache[PATH_MAX]; -}; - /* A file entry went away or appeared */ static void diff_index_show_file(struct rev_info *revs, const char *prefix, @@ -216,8 +209,7 @@ static void diff_index_show_file(struct rev_info *revs, static int get_stat_data(struct cache_entry *ce, const unsigned char **sha1p, unsigned int *modep, - int cached, int match_missing, - struct oneway_unpack_data *cbdata) + int cached, int match_missing) { const unsigned char *sha1 = ce->sha1; unsigned int mode = ce->ce_mode; @@ -248,25 +240,24 @@ static int get_stat_data(struct cache_entry *ce, return 0; } -static void show_new_file(struct oneway_unpack_data *cbdata, +static void show_new_file(struct rev_info *revs, struct cache_entry *new, int cached, int match_missing) { const unsigned char *sha1; unsigned int mode; - struct rev_info *revs = cbdata->revs; /* * New file in the index: it might actually be different in * the working copy. */ - if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0) + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) return; diff_index_show_file(revs, "+", new, sha1, mode); } -static int show_modified(struct oneway_unpack_data *cbdata, +static int show_modified(struct rev_info *revs, struct cache_entry *old, struct cache_entry *new, int report_missing, @@ -274,9 +265,8 @@ static int show_modified(struct oneway_unpack_data *cbdata, { unsigned int mode, oldmode; const unsigned char *sha1; - struct rev_info *revs = cbdata->revs; - if (get_stat_data(new, &sha1, &mode, cached, match_missing, cbdata) < 0) { + if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) { if (report_missing) diff_index_show_file(revs, "-", old, old->sha1, old->ce_mode); @@ -344,8 +334,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, struct cache_entry *idx, struct cache_entry *tree) { - struct oneway_unpack_data *cbdata = o->unpack_data; - struct rev_info *revs = cbdata->revs; + struct rev_info *revs = o->unpack_data; int match_missing, cached; /* @@ -368,7 +357,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, * Something added to the tree? */ if (!tree) { - show_new_file(cbdata, idx, cached, match_missing); + show_new_file(revs, idx, cached, match_missing); return; } @@ -381,7 +370,7 @@ static void do_oneway_diff(struct unpack_trees_options *o, } /* Show difference between old and new */ - show_modified(cbdata, tree, idx, 1, cached, match_missing); + show_modified(revs, tree, idx, 1, cached, match_missing); } static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o) @@ -418,8 +407,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o) { struct cache_entry *idx = src[0]; struct cache_entry *tree = src[1]; - struct oneway_unpack_data *cbdata = o->unpack_data; - struct rev_info *revs = cbdata->revs; + struct rev_info *revs = o->unpack_data; if (idx && ce_stage(idx)) skip_same_name(idx, o); @@ -446,7 +434,6 @@ int run_diff_index(struct rev_info *revs, int cached) const char *tree_name; struct unpack_trees_options opts; struct tree_desc t; - struct oneway_unpack_data unpack_cb; mark_merge_entries(); @@ -456,14 +443,12 @@ int run_diff_index(struct rev_info *revs, int cached) if (!tree) return error("bad tree object %s", tree_name); - unpack_cb.revs = revs; - unpack_cb.symcache[0] = '\0'; memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; opts.index_only = cached; opts.merge = 1; opts.fn = oneway_diff; - opts.unpack_data = &unpack_cb; + opts.unpack_data = revs; opts.src_index = &the_index; opts.dst_index = NULL; @@ -486,7 +471,6 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) struct cache_entry *last = NULL; struct unpack_trees_options opts; struct tree_desc t; - struct oneway_unpack_data unpack_cb; /* * This is used by git-blame to run diff-cache internally; @@ -515,14 +499,12 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) if (!tree) die("bad tree object %s", sha1_to_hex(tree_sha1)); - unpack_cb.revs = &revs; - unpack_cb.symcache[0] = '\0'; memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; opts.fn = oneway_diff; - opts.unpack_data = &unpack_cb; + opts.unpack_data = &revs; opts.src_index = &the_index; opts.dst_index = &the_index; @@ -531,3 +513,18 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt) exit(128); return 0; } + +int index_differs_from(const char *def, int diff_flags) +{ + struct rev_info rev; + + init_revisions(&rev, NULL); + setup_revisions(0, NULL, &rev, def); + DIFF_OPT_SET(&rev.diffopt, QUIET); + DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS); + rev.diffopt.flags |= diff_flags; + run_diff_index(&rev, 1); + if (rev.pending.alloc) + free(rev.pending.objects); + return (DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES) != 0); +} diff --git a/diff-no-index.c b/diff-no-index.c index a3e47a76e4..7273a7a0b6 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; + + /* find the end of the word */ + *end = *begin + 1; + while (*end < buffer->size && !isspace(buffer->ptr[*end])) + (*end)++; - if (eol) { - if (suppress_newline) - buffer->suppressed_newline = 1; - else - putc('\n', file); - } + 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) @@ -1471,6 +1565,7 @@ static void builtin_diff(const char *name_a, ecbdata.file = o->file; xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts; xecfg.ctxlen = o->context; + xecfg.interhunkctxlen = o->interhunkcontext; xecfg.flags = XDL_EMIT_FUNCNAMES; if (pe) xdiff_set_find_func(&xecfg, pe->pattern, pe->cflags); @@ -1484,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); @@ -1846,17 +1956,23 @@ void diff_free_filespec_data(struct diff_filespec *s) s->cnt_data = NULL; } -static void prep_temp_blob(struct diff_tempfile *temp, +static void prep_temp_blob(const char *path, struct diff_tempfile *temp, void *blob, unsigned long size, const unsigned char *sha1, int mode) { int fd; + struct strbuf buf = STRBUF_INIT; fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX"); if (fd < 0) die("unable to create temp-file: %s", strerror(errno)); + if (convert_to_working_tree(path, + (const char *)blob, (size_t)size, &buf)) { + blob = buf.buf; + size = buf.len; + } if (write_in_full(fd, blob, size) != size) die("unable to write temp-file"); close(fd); @@ -1864,12 +1980,14 @@ static void prep_temp_blob(struct diff_tempfile *temp, strcpy(temp->hex, sha1_to_hex(sha1)); temp->hex[40] = 0; sprintf(temp->mode, "%06o", mode); + strbuf_release(&buf); } -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 @@ -1878,7 +1996,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 || @@ -1897,7 +2021,7 @@ static void prepare_temp_file(const char *name, die("readlink(%s)", name); if (ret == sizeof(buf)) die("symlink too long: %s", name); - prep_temp_blob(temp, buf, ret, + prep_temp_blob(name, temp, buf, ret, (one->sha1_valid ? one->sha1 : null_sha1), (one->sha1_valid ? @@ -1918,32 +2042,15 @@ static void prepare_temp_file(const char *name, */ sprintf(temp->mode, "%06o", one->mode); } - return; + return temp; } else { if (diff_populate_filespec(one, 0)) die("cannot read data blob for %s", one->path); - prep_temp_blob(temp, one->data, one->size, + prep_temp_blob(name, 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: @@ -1961,34 +2068,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; @@ -2119,7 +2214,7 @@ static void diff_fill_sha1_info(struct diff_filespec *one) if (lstat(one->path, &st) < 0) die("stat %s", one->path); if (index_path(one->sha1, one->path, &st, 0)) - die("cannot hash %s\n", one->path); + die("cannot hash %s", one->path); } } else @@ -2246,15 +2341,12 @@ void diff_setup(struct diff_options *options) options->break_opt = -1; options->rename_limit = -1; options->dirstat_percent = 3; - DIFF_OPT_CLR(options, DIRSTAT_CUMULATIVE); options->context = 3; options->change = diff_change; options->add_remove = diff_addremove; if (diff_use_color_default > 0) DIFF_OPT_SET(options, COLOR_DIFF); - else - DIFF_OPT_CLR(options, COLOR_DIFF); options->detect_rename = diff_detect_rename_default; if (!diff_mnemonic_prefix) { @@ -2495,6 +2587,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")) { @@ -2517,6 +2611,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")) @@ -2562,6 +2660,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->b_prefix = arg + 13; else if (!strcmp(arg, "--no-prefix")) options->a_prefix = options->b_prefix = ""; + else if (opt_arg(arg, '\0', "inter-hunk-context", + &options->interhunkcontext)) + ; else if (!prefixcmp(arg, "--output=")) { options->file = fopen(arg + strlen("--output="), "w"); options->close_file = 1; @@ -3468,15 +3569,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)); @@ -3485,13 +3586,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); } @@ -78,6 +78,7 @@ struct diff_options { const char *a_prefix, *b_prefix; unsigned flags; int context; + int interhunkcontext; int break_opt; int detect_rename; int skip_stat_unmatch; @@ -97,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; @@ -263,4 +265,6 @@ extern int diff_result_code(struct diff_options *, int); extern void diff_no_index(struct rev_info *, int, const char **, int, const char *); +extern int index_differs_from(const char *def, int diff_flags); + #endif /* DIFF_H */ diff --git a/diffcore-pickaxe.c b/diffcore-pickaxe.c index af9fffe6e8..d0ef839700 100644 --- a/diffcore-pickaxe.c +++ b/diffcore-pickaxe.c @@ -10,7 +10,7 @@ static unsigned int contains(struct diff_filespec *one, regex_t *regexp) { unsigned int cnt; - unsigned long offset, sz; + unsigned long sz; const char *data; if (diff_populate_filespec(one, 0)) return 0; @@ -25,23 +25,23 @@ static unsigned int contains(struct diff_filespec *one, regmatch_t regmatch; int flags = 0; + assert(data[sz] == '\0'); while (*data && !regexec(regexp, data, 1, ®match, flags)) { flags |= REG_NOTBOL; - data += regmatch.rm_so; - if (*data) data++; + data += regmatch.rm_eo; + if (*data && regmatch.rm_so == regmatch.rm_eo) + data++; cnt++; } } else { /* Classic exact string match */ - /* Yes, I've heard of strstr(), but the thing is *data may - * not be NUL terminated. Sue me. - */ - for (offset = 0; offset + len <= sz; offset++) { - /* we count non-overlapping occurrences of needle */ - if (!memcmp(needle, data + offset, len)) { - offset += len - 1; - cnt++; - } + while (sz) { + const char *found = memmem(data, sz, needle, len); + if (!found) + break; + sz -= found - data + len; + data = found + len; + cnt++; } } diff_free_filespec_data(one); @@ -75,7 +75,7 @@ static int match_one(const char *match, const char *name, int namelen) for (;;) { unsigned char c1 = *match; unsigned char c2 = *name; - if (isspecial(c1)) + if (c1 == '\0' || is_glob_special(c1)) break; if (c1 != c2) return 0; @@ -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; @@ -585,10 +588,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co int len, dtype; int exclude; - if ((de->d_name[0] == '.') && - (de->d_name[1] == 0 || - !strcmp(de->d_name + 1, ".") || - !strcmp(de->d_name + 1, "git"))) + if (is_dot_or_dotdot(de->d_name) || + !strcmp(de->d_name, ".git")) continue; len = strlen(de->d_name); /* Ignore overly long pathnames! */ @@ -680,7 +681,7 @@ static int simple_length(const char *match) for (;;) { unsigned char c = *match++; len++; - if (isspecial(c)) + if (c == '\0' || is_glob_special(c)) return len; } } @@ -779,6 +780,25 @@ int is_inside_dir(const char *dir) return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL; } +int is_empty_dir(const char *path) +{ + DIR *dir = opendir(path); + struct dirent *e; + int ret = 1; + + if (!dir) + return 0; + + while ((e = readdir(dir)) != NULL) + if (!is_dot_or_dotdot(e->d_name)) { + ret = 0; + break; + } + + closedir(dir); + return ret; +} + int remove_dir_recursively(struct strbuf *path, int only_empty) { DIR *dir = opendir(path->buf); @@ -793,10 +813,8 @@ int remove_dir_recursively(struct strbuf *path, int only_empty) len = path->len; while ((e = readdir(dir)) != NULL) { struct stat st; - if ((e->d_name[0] == '.') && - ((e->d_name[1] == 0) || - ((e->d_name[1] == '.') && e->d_name[2] == 0))) - continue; /* "." and ".." */ + if (is_dot_or_dotdot(e->d_name)) + continue; strbuf_setlen(path, len); strbuf_addstr(path, e->d_name); @@ -77,6 +77,15 @@ extern int file_exists(const char *); extern char *get_relative_cwd(char *buffer, int size, const char *dir); extern int is_inside_dir(const char *dir); +static inline int is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + +extern int is_empty_dir(const char *dir); + extern void setup_standard_excludes(struct dir_struct *dir); extern int remove_dir_recursively(struct strbuf *path, int only_empty); @@ -1,5 +1,6 @@ #include "cache.h" #include "blob.h" +#include "dir.h" static void create_directories(const char *path, const struct checkout *state) { @@ -8,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 && @@ -62,9 +53,7 @@ static void remove_subtree(const char *path) *name++ = '/'; while ((de = readdir(dir)) != NULL) { struct stat st; - if ((de->d_name[0] == '.') && - ((de->d_name[1] == 0) || - ((de->d_name[1] == '.') && de->d_name[2] == 0))) + if (is_dot_or_dotdot(de->d_name)) continue; strcpy(name, de->d_name); if (lstat(pathbuf, &st)) diff --git a/exec_cmd.c b/exec_cmd.c index 351fec2e9e..408e4e55e1 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -9,17 +9,53 @@ 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 && + !(prefix = strip_path_suffix(argv0_path, GIT_EXEC_PATH)) && + !(prefix = strip_path_suffix(argv0_path, BINDIR)) && + !(prefix = strip_path_suffix(argv0_path, "git"))) { + 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) @@ -65,9 +101,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 594f961387..e2b546b615 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 f246d5347b..d5fc042bbf 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) @@ -867,7 +868,7 @@ static char *create_index(void) /* Generate the fan-out array. */ c = idx; for (i = 0; i < 256; i++) { - struct object_entry **next = c;; + struct object_entry **next = c; while (next < last) { if ((*next)->sha1[0] != i) break; @@ -901,9 +902,6 @@ static char *keep_pack(char *curr_index_name) static const char *keep_msg = "fast-import"; int keep_fd; - chmod(pack_data->pack_name, 0444); - chmod(curr_index_name, 0444); - keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1); if (keep_fd < 0) die("cannot create keep file"); @@ -2403,6 +2401,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 b0223c3419..5407b2e1b8 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -3,6 +3,8 @@ use strict; use Git; +binmode(STDOUT, ":raw"); + my $repo = Git->repository(); my $menu_use_color = $repo->get_colorbool('color.interactive'); @@ -12,6 +14,13 @@ my ($prompt_color, $header_color, $help_color) = $repo->get_color('color.interactive.header', 'bold'), $repo->get_color('color.interactive.help', 'red bold'), ) : (); +my $error_color = (); +if ($menu_use_color) { + my $help_color_spec = ($repo->config('color.interactive.help') or + 'red bold'); + $error_color = $repo->get_color('color.interactive.error', + $help_color_spec); +} my $diff_use_color = $repo->get_colorbool('color.diff'); my ($fraginfo_color) = @@ -33,6 +42,17 @@ my ($diff_new_color) = my $normal_color = $repo->get_color("", "reset"); +my $use_readkey = 0; +sub ReadMode; +sub ReadKey; +if ($repo->config_bool("interactive.singlekey")) { + eval { + require Term::ReadKey; + Term::ReadKey->import; + $use_readkey = 1; + }; +} + sub colored { my $color = shift; my $string = join("", @_); @@ -73,6 +93,47 @@ if (!defined $GIT_DIR) { } chomp($GIT_DIR); +my %cquote_map = ( + "b" => chr(8), + "t" => chr(9), + "n" => chr(10), + "v" => chr(11), + "f" => chr(12), + "r" => chr(13), + "\\" => "\\", + "\042" => "\042", +); + +sub unquote_path { + local ($_) = @_; + my ($retval, $remainder); + if (!/^\042(.*)\042$/) { + return $_; + } + ($_, $retval) = ($1, ""); + while (/^([^\\]*)\\(.*)$/) { + $remainder = $2; + $retval .= $1; + for ($remainder) { + if (/^([0-3][0-7][0-7])(.*)$/) { + $retval .= chr(oct($1)); + $_ = $2; + last; + } + if (/^([\\\042btnvfr])(.*)$/) { + $retval .= $cquote_map{$1}; + $_ = $2; + last; + } + # This is malformed -- just return it as-is for now. + return $_[0]; + } + $_ = $remainder; + } + $retval .= $_; + return $retval; +} + sub refresh { my $fh; open $fh, 'git update-index --refresh |' @@ -86,7 +147,7 @@ sub refresh { sub list_untracked { map { chomp $_; - $_; + unquote_path($_); } run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV); } @@ -123,7 +184,8 @@ sub list_modified { if (@ARGV) { @tracked = map { - chomp $_; $_; + chomp $_; + unquote_path($_); } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV); return if (!@tracked); } @@ -135,6 +197,7 @@ sub list_modified { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { my ($change, $bin); + $file = unquote_path($file); if ($add eq '-' && $del eq '-') { $change = 'binary'; $bin = 1; @@ -150,6 +213,7 @@ sub list_modified { } elsif (($adddel, $file) = /^ (create|delete) mode [0-7]+ (.*)$/) { + $file = unquote_path($file); $data{$file}{INDEX_ADDDEL} = $adddel; } } @@ -157,6 +221,7 @@ sub list_modified { for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) { if (($add, $del, $file) = /^([-\d]+) ([-\d]+) (.*)/) { + $file = unquote_path($file); if (!exists $data{$file}) { $data{$file} = +{ INDEX => 'unchanged', @@ -178,6 +243,7 @@ sub list_modified { } elsif (($adddel, $file) = /^ (create|delete) mode [0-7]+ (.*)$/) { + $file = unquote_path($file); $data{$file}{FILE_ADDDEL} = $adddel; } } @@ -284,7 +350,8 @@ sub find_unique_prefixes { } %search = %{$search{$letter}}; } - if ($soft_limit && $j + 1 > $soft_limit) { + if (ord($letters[0]) > 127 || + ($soft_limit && $j + 1 > $soft_limit)) { $prefix = undef; $remainder = $ret; } @@ -325,6 +392,10 @@ sub highlight_prefix { return "$prompt_color$prefix$normal_color$remainder"; } +sub error_msg { + print STDERR colored $error_color, @_; +} + sub list_and_choose { my ($opts, @stuff) = @_; my (@chosen, @return); @@ -420,12 +491,12 @@ sub list_and_choose { else { $bottom = $top = find_unique($choice, @stuff); if (!defined $bottom) { - print "Huh ($choice)?\n"; + error_msg "Huh ($choice)?\n"; next TOPLOOP; } } if ($opts->{SINGLETON} && $bottom != $top) { - print "Huh ($choice)?\n"; + error_msg "Huh ($choice)?\n"; next TOPLOOP; } for ($i = $bottom-1; $i <= $top-1; $i++) { @@ -549,11 +620,12 @@ sub parse_diff { if ($diff_use_color) { @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path); } - my (@hunk) = { TEXT => [], DISPLAY => [] }; + my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' }; for (my $i = 0; $i < @diff; $i++) { if ($diff[$i] =~ /^@@ /) { - push @hunk, { TEXT => [], DISPLAY => [] }; + push @hunk, { TEXT => [], DISPLAY => [], + TYPE => 'hunk' }; } push @{$hunk[-1]{TEXT}}, $diff[$i]; push @{$hunk[-1]{DISPLAY}}, @@ -565,8 +637,8 @@ sub parse_diff { sub parse_diff_header { my $src = shift; - my $head = { TEXT => [], DISPLAY => [] }; - my $mode = { TEXT => [], DISPLAY => [] }; + my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' }; + my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' }; for (my $i = 0; $i < @{$src->{TEXT}}; $i++) { my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? @@ -613,6 +685,7 @@ sub split_hunk { my $this = +{ TEXT => [], DISPLAY => [], + TYPE => 'hunk', OLD => $o_ofs, NEW => $n_ofs, OCNT => 0, @@ -758,11 +831,32 @@ sub diff_applies { return close $fh; } +sub _restore_terminal_and_die { + ReadMode 'restore'; + print "\n"; + exit 1; +} + +sub prompt_single_character { + if ($use_readkey) { + local $SIG{TERM} = \&_restore_terminal_and_die; + local $SIG{INT} = \&_restore_terminal_and_die; + ReadMode 'cbreak'; + my $key = ReadKey 0; + ReadMode 'restore'; + print "$key" if defined $key; + print "\n"; + return $key; + } else { + return <STDIN>; + } +} + sub prompt_yesno { my ($prompt) = @_; while (1) { print colored $prompt_color, $prompt; - my $line = <STDIN>; + my $line = prompt_single_character; return 0 if $line =~ /^n/i; return 1 if $line =~ /^y/i; } @@ -777,7 +871,11 @@ sub edit_hunk_loop { if (!defined $text) { return undef; } - my $newhunk = { TEXT => $text, USE => 1 }; + my $newhunk = { + TEXT => $text, + TYPE => $hunk->[$ix]->{TYPE}, + USE => 1 + }; if (diff_applies($head, @{$hunk}[0..$ix-1], $newhunk, @@ -798,8 +896,11 @@ sub help_patch_cmd { print colored $help_color, <<\EOF ; y - stage this hunk n - do not stage this hunk +q - quit, do not stage this hunk nor any of the remaining ones a - stage this and all the remaining hunks in the file d - do not stage this hunk nor any of the remaining hunks in the file +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 @@ -832,11 +933,53 @@ sub patch_update_cmd { @mods); } for (@them) { - patch_update_file($_->{VALUE}); + return 0 if patch_update_file($_->{VALUE}); + } +} + +# Generate a one line summary of a hunk. +sub summarize_hunk { + my $rhunk = shift; + my $summary = $rhunk->{TEXT}[0]; + + # Keep the line numbers, discard extra context. + $summary =~ s/@@(.*?)@@.*/$1 /s; + $summary .= " " x (20 - length $summary); + + # Add some user context. + for my $line (@{$rhunk->{TEXT}}) { + if ($line =~ m/^[+-].*\w/) { + $summary .= $line; + last; + } + } + + chomp $summary; + return substr($summary, 0, 80) . "\n"; +} + + +# Print a one-line summary of each hunk in the array ref in +# the first argument, starting wih the index in the 2nd. +sub display_hunks { + my ($hunks, $i) = @_; + my $ctr = 0; + $i ||= 0; + for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) { + my $status = " "; + if (defined $hunks->[$i]{USE}) { + $status = $hunks->[$i]{USE} ? "+" : "-"; + } + printf "%s%2d: %s", + $status, + $i + 1, + summarize_hunk($hunks->[$i]); } + return $i; } sub patch_update_file { + my $quit = 0; my ($ix, $num); my $path = shift; my ($head, @hunk) = parse_diff($path); @@ -846,32 +989,7 @@ sub patch_update_file { } if (@{$mode->{TEXT}}) { - while (1) { - print @{$mode->{DISPLAY}}; - print colored $prompt_color, - "Stage mode change [y/n/a/d/?]? "; - my $line = <STDIN>; - if ($line =~ /^y/i) { - $mode->{USE} = 1; - last; - } - elsif ($line =~ /^n/i) { - $mode->{USE} = 0; - last; - } - elsif ($line =~ /^a/i) { - $_->{USE} = 1 foreach ($mode, @hunk); - last; - } - elsif ($line =~ /^d/i) { - $_->{USE} = 0 foreach ($mode, @hunk); - last; - } - else { - help_patch_cmd(''); - next; - } - } + unshift @hunk, $mode; } $num = scalar @hunk; @@ -887,22 +1005,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'; } for ($i = 0; $i < $num; $i++) { if (!defined $hunk[$i]{USE}) { @@ -912,15 +1033,20 @@ sub patch_update_file { } last if (!$undecided); - if (hunk_splittable($hunk[$ix]{TEXT})) { - $other .= '/s'; + if ($hunk[$ix]{TYPE} eq 'hunk' && + hunk_splittable($hunk[$ix]{TEXT})) { + $other .= ',s'; + } + if ($hunk[$ix]{TYPE} eq 'hunk') { + $other .= ',e'; } - $other .= '/e'; for (@{$hunk[$ix]{DISPLAY}}) { print; } - print colored $prompt_color, "Stage this hunk [y/n/a/d$other/?]? "; - my $line = <STDIN>; + print colored $prompt_color, 'Stage ', + ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'), + " [y,n,q,a,d,/$other,?]? "; + my $line = prompt_single_character; if ($line) { if ($line =~ /^y/i) { $hunk[$ix]{USE} = 1; @@ -937,6 +1063,31 @@ sub patch_update_file { } next; } + elsif ($other =~ /g/ && $line =~ /^g(.*)/) { + my $response = $1; + my $no = $ix > 10 ? $ix - 10 : 0; + while ($response eq '') { + my $extra = ""; + $no = display_hunks(\@hunk, $no); + if ($no < $num) { + $extra = " (<ret> to see more)"; + } + print "go to which hunk$extra? "; + $response = <STDIN>; + if (!defined $response) { + $response = ''; + } + chomp $response; + } + if ($response !~ /^\s*\d+\s*$/) { + error_msg "Invalid number: '$response'\n"; + } elsif (0 < $response && $response <= $num) { + $ix = $response - 1; + } else { + error_msg "Sorry, only $num hunks available.\n"; + } + next; + } elsif ($line =~ /^d/i) { while ($ix < $num) { if (!defined $hunk[$ix]{USE}) { @@ -946,30 +1097,86 @@ sub patch_update_file { } next; } - elsif ($other =~ /K/ && $line =~ /^K/) { - $ix--; + elsif ($line =~ /^q/i) { + while ($ix < $num) { + if (!defined $hunk[$ix]{USE}) { + $hunk[$ix]{USE} = 0; + } + $ix++; + } + $quit = 1; next; } - elsif ($other =~ /J/ && $line =~ /^J/) { - $ix++; + elsif ($line =~ m|^/(.*)|) { + my $regex = $1; + if ($1 eq "") { + print colored $prompt_color, "search for regex? "; + $regex = <STDIN>; + if (defined $regex) { + chomp $regex; + } + } + my $search_string; + eval { + $search_string = qr{$regex}m; + }; + if ($@) { + my ($err,$exp) = ($@, $1); + $err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//; + error_msg "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) { + error_msg "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 { + error_msg "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 { + error_msg "No next hunk\n"; } next; } + elsif ($line =~ /^k/) { + if ($other =~ /k/) { + while (1) { + $ix--; + last if (!$ix || + !defined $hunk[$ix]{USE}); + } + } + else { + error_msg "No previous hunk\n"; + } + next; + } + elsif ($line =~ /^j/) { + if ($other !~ /j/) { + error_msg "No next hunk\n"; + next; + } + } elsif ($other =~ /s/ && $line =~ /^s/) { my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY}); if (1 < @split) { @@ -980,7 +1187,7 @@ sub patch_update_file { $num = scalar @hunk; next; } - elsif ($line =~ /^e/) { + elsif ($other =~ /e/ && $line =~ /^e/) { my $newhunk = edit_hunk_loop($head, \@hunk, $ix); if (defined $newhunk) { splice @hunk, $ix, 1, $newhunk; @@ -1001,9 +1208,6 @@ sub patch_update_file { my $n_lofs = 0; my @result = (); - if ($mode->{USE}) { - push @result, @{$mode->{TEXT}}; - } for (@hunk) { if ($_->{USE}) { push @result, @{$_->{TEXT}}; @@ -1026,6 +1230,7 @@ sub patch_update_file { } print "\n"; + return $quit; } sub diff_cmd { @@ -8,21 +8,24 @@ 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) k,keep pass -k flag to git-mailinfo whitespace= pass it through git-apply +directory= pass it through git-apply C= pass it through git-apply p= pass it through git-apply +reject pass it through git-apply 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) @@ -33,6 +36,14 @@ cd_to_toplevel git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first" +sq () { + for sqarg + do + printf "%s" "$sqarg" | + sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/' + done +} + stop_here () { echo "$1" >"$dotest/next" exit 1 @@ -124,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 @@ -155,10 +168,16 @@ do ;; --resolvemsg) shift; resolvemsg=$1 ;; - --whitespace) - git_apply_opt="$git_apply_opt $1=$2"; shift ;; + --whitespace|--directory) + git_apply_opt="$git_apply_opt $(sq "$1=$2")"; shift ;; -C|-p) - git_apply_opt="$git_apply_opt $1$2"; shift ;; + 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 ;; *) @@ -192,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 @@ -202,6 +221,9 @@ then resume=yes case "$skip,$abort" in + t,t) + die "Please make up your mind. --skip or --abort?" + ;; t,) git rerere clear git read-tree --reset -u HEAD HEAD @@ -210,12 +232,19 @@ then git update-ref ORIG_HEAD $orig_head ;; ,t) + if test -f "$dotest/rebasing" + then + exec git rebase --abort + fi git rerere clear - git read-tree --reset -u HEAD ORIG_HEAD - git reset ORIG_HEAD + test -f "$dotest/dirtyindex" || { + git read-tree --reset -u HEAD ORIG_HEAD + git reset ORIG_HEAD + } rm -fr "$dotest" exit ;; esac + rm -f "$dotest/dirtyindex" else # Make sure we are not given --skip, --resolved, nor --abort test "$skip$resolved$abort" = "" || @@ -268,9 +297,10 @@ 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 + if test "$files" + then + : >"$dotest/dirtyindex" + die "Dirty index: cannot apply patches (dirty: $files)" fi esac @@ -459,7 +489,7 @@ do case "$resolved" in '') - git apply $git_apply_opt --index "$dotest/patch" + eval 'git apply '"$git_apply_opt"' --index "$dotest/patch"' apply_status=$? ;; t) @@ -501,7 +531,7 @@ do fi if test $apply_status != 0 then - echo Patch failed at $msgnum. + printf 'Patch failed at %s %s\n' "$msgnum" "$FIRSTLINE" stop_here_user_resolve $this fi @@ -512,7 +542,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-compat-util.h b/git-compat-util.h index 124bb94b15..878d83dd08 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -319,6 +319,7 @@ static inline int has_extension(const char *filename, const char *ext) } /* Sane ctype - no locale, and works with signed chars */ +#undef isascii #undef isspace #undef isdigit #undef isalpha @@ -329,13 +330,16 @@ extern unsigned char sane_ctype[256]; #define GIT_SPACE 0x01 #define GIT_DIGIT 0x02 #define GIT_ALPHA 0x04 -#define GIT_SPECIAL 0x08 +#define GIT_GLOB_SPECIAL 0x08 +#define GIT_REGEX_SPECIAL 0x10 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) +#define isascii(x) (((x) & ~0x7f) == 0) #define isspace(x) sane_istest(x,GIT_SPACE) #define isdigit(x) sane_istest(x,GIT_DIGIT) #define isalpha(x) sane_istest(x,GIT_ALPHA) #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT) -#define isspecial(x) sane_istest(x,GIT_SPECIAL) +#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) +#define is_regex_special(x) sane_istest(x,GIT_GLOB_SPECIAL | GIT_REGEX_SPECIAL) #define tolower(x) sane_case((unsigned char)(x), 0x20) #define toupper(x) sane_case((unsigned char)(x), 0) diff --git a/git-cvsserver.perl b/git-cvsserver.perl index b0a805c688..ab6cea3e53 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, @@ -1358,7 +1359,13 @@ sub req_ci # write our commit message out if we have one ... my ( $msg_fh, $msg_filename ) = tempfile( DIR => $TEMP_DIR ); print $msg_fh $state->{opt}{m};# if ( exists ( $state->{opt}{m} ) ); - print $msg_fh "\n\nvia git-CVS emulator\n"; + if ( defined ( $cfg->{gitcvs}{commitmsgannotation} ) ) { + if ($cfg->{gitcvs}{commitmsgannotation} !~ /^\s*$/ ) { + print $msg_fh "\n\n".$cfg->{gitcvs}{commitmsgannotation}."\n" + } + } else { + print $msg_fh "\n\nvia git-CVS emulator\n"; + } close $msg_fh; my $commithash = `git-commit-tree $treehash -p $parenthash < $msg_filename`; @@ -1407,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 ) { @@ -2527,12 +2534,18 @@ sub open_blob_or_die return $fh; } -# Generate a CVS author name from Git author information, by taking -# the first eight characters of the user part of the email address. +# Generate a CVS author name from Git author information, by taking the local +# part of the email address and replacing characters not in the Portable +# Filename Character Set (see IEEE Std 1003.1-2001, 3.276) by underscores. CVS +# Login names are Unix login names, which should be restricted to this +# character set. sub cvs_author { my $author_line = shift; - (my $author) = $author_line =~ /<([^>@]{1,8})/; + (my $author) = $author_line =~ /<([^@>]*)/; + + $author =~ s/[^-a-zA-Z0-9_.]/_/g; + $author =~ s/^-/_/; $author; } diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 0897b5971a..9a09ba1382 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -40,6 +40,16 @@ skip_commit() done; } +# if you run 'git_commit_non_empty_tree "$@"' in a commit filter, +# it will skip commits that leave the tree untouched, commit the other. +git_commit_non_empty_tree() +{ + if test $# = 3 && test "$1" = $(git rev-parse "$3^{tree}"); then + map "$3" + else + git commit-tree "$@" + fi +} # override die(): this version puts in an extra line break, so that # the progress is still visible @@ -109,11 +119,12 @@ filter_tree= filter_index= filter_parent= filter_msg=cat -filter_commit='git commit-tree "$@"' +filter_commit= filter_tag_name= filter_subdir= orig_namespace=refs/original/ force= +prune_empty= while : do case "$1" in @@ -126,6 +137,11 @@ do force=t continue ;; + --prune-empty) + shift + prune_empty=t + continue + ;; -*) ;; *) @@ -176,6 +192,17 @@ do esac done +case "$prune_empty,$filter_commit" in +,) + filter_commit='git commit-tree "$@"';; +t,) + filter_commit="$functions;"' git_commit_non_empty_tree "$@"';; +,*) + ;; +*) + die "Cannot set --prune-empty and --filter-commit at the same time" +esac + case "$force" in t) rm -rf "$tempdir" @@ -193,8 +220,14 @@ die "" # Remove tempdir on exit trap 'cd ../..; rm -rf "$tempdir"' 0 +ORIG_GIT_DIR="$GIT_DIR" +ORIG_GIT_WORK_TREE="$GIT_WORK_TREE" +ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE" +GIT_WORK_TREE=. +export GIT_DIR GIT_WORK_TREE + # Make sure refs/original is empty -git for-each-ref > "$tempdir"/backup-refs +git for-each-ref > "$tempdir"/backup-refs || exit while read sha1 type name do case "$force,$name" in @@ -207,15 +240,10 @@ do esac done < "$tempdir"/backup-refs -ORIG_GIT_DIR="$GIT_DIR" -ORIG_GIT_WORK_TREE="$GIT_WORK_TREE" -ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE" -GIT_WORK_TREE=. -export GIT_DIR GIT_WORK_TREE - # The refs should be updated if their heads were rewritten -git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" | -sed -e '/^^/d' >"$tempdir"/heads +git rev-parse --no-flags --revs-only --symbolic-full-name \ + --default HEAD "$@" > "$tempdir"/raw-heads || exit +sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads test -s "$tempdir"/heads || die "Which ref do you want to rewrite?" @@ -224,8 +252,6 @@ GIT_INDEX_FILE="$(pwd)/../index" export GIT_INDEX_FILE git read-tree || die "Could not seed the index" -ret=0 - # map old->new commit ids for rewriting parents mkdir ../map || die "Could not create map/ directory" @@ -288,10 +314,11 @@ while read commit parents; do die "tree filter failed: $filter_tree" ( - git diff-index -r --name-only $commit + git diff-index -r --name-only $commit && git ls-files --others - ) | - git update-index --add --replace --remove --stdin + ) > "$tempdir"/tree-state || exit + git update-index --add --replace --remove --stdin \ + < "$tempdir"/tree-state || exit fi eval "$filter_index" < /dev/null || @@ -312,7 +339,8 @@ while read commit parents; do eval "$filter_msg" > ../message || die "msg filter failed: $filter_msg" @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \ - $(git write-tree) $parentstr < ../message > ../map/$commit + $(git write-tree) $parentstr < ../message > ../map/$commit || + die "could not write rewritten commit" done <../revs # In case of a subdirectory filter, it is possible that a specified head @@ -380,7 +408,8 @@ do die "Could not rewrite $ref" ;; esac - git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 + git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 || + exit done < "$tempdir"/heads # TODO: This should possibly go, with the semantics that all positive given @@ -456,7 +485,7 @@ test -z "$ORIG_GIT_INDEX_FILE" || { } if [ "$(is_bare_repository)" = false ]; then - git read-tree -u -m HEAD + git read-tree -u -m HEAD || exit fi -exit $ret +exit 0 diff --git a/git-mergetool.sh b/git-mergetool.sh index d4078a6aff..87fa88af55 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -8,12 +8,11 @@ # at the discretion of Junio C Hamano. # -USAGE='[--tool=tool] [file to merge] ...' +USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= . git-sh-setup require_work_tree -prefix=$(git rev-parse --show-prefix) # Returns true if the mode reflects a symlink is_symlink () { @@ -70,16 +69,16 @@ resolve_symlink_merge () { git checkout-index -f --stage=2 -- "$MERGED" git add -- "$MERGED" cleanup_temp_files --save-backup - return + return 0 ;; [rR]*) git checkout-index -f --stage=3 -- "$MERGED" git add -- "$MERGED" cleanup_temp_files --save-backup - return + return 0 ;; [aA]*) - exit 1 + return 1 ;; esac done @@ -97,15 +96,15 @@ resolve_deleted_merge () { [mMcC]*) git add -- "$MERGED" cleanup_temp_files --save-backup - return + return 0 ;; [dD]*) git rm -- "$MERGED" > /dev/null cleanup_temp_files - return + return 0 ;; [aA]*) - exit 1 + return 1 ;; esac done @@ -127,6 +126,14 @@ check_unchanged () { fi } +checkout_staged_file () { + tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ') + + if test $? -eq 0 -a -n "$tmpfile" ; then + mv -- "$(git rev-parse --show-cdup)$tmpfile" "$3" + fi +} + merge_file () { MERGED="$1" @@ -137,7 +144,7 @@ merge_file () { else echo "$MERGED: file does not need merging" fi - exit 1 + return 1 fi ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')" @@ -153,9 +160,9 @@ merge_file () { local_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==2) print $1;}'` remote_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==3) print $1;}'` - base_present && git cat-file blob ":1:$prefix$MERGED" >"$BASE" 2>/dev/null - local_present && git cat-file blob ":2:$prefix$MERGED" >"$LOCAL" 2>/dev/null - remote_present && git cat-file blob ":3:$prefix$MERGED" >"$REMOTE" 2>/dev/null + base_present && checkout_staged_file 1 "$MERGED" "$BASE" + local_present && checkout_staged_file 2 "$MERGED" "$LOCAL" + remote_present && checkout_staged_file 3 "$MERGED" "$REMOTE" if test -z "$local_mode" -o -z "$remote_mode"; then echo "Deleted merge conflict for '$MERGED':" @@ -176,8 +183,10 @@ merge_file () { echo "Normal merge conflict for '$MERGED':" describe_file "$local_mode" "local" "$LOCAL" describe_file "$remote_mode" "remote" "$REMOTE" - printf "Hit return to start merge resolution tool (%s): " "$merge_tool" - read ans + if "$prompt" = true; then + printf "Hit return to start merge resolution tool (%s): " "$merge_tool" + read ans + fi case "$merge_tool" in kdiff3) @@ -198,14 +207,19 @@ merge_file () { fi status=$? ;; - meld|vimdiff) + meld) touch "$BACKUP" "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" check_unchanged ;; + vimdiff) + touch "$BACKUP" + "$merge_tool_path" -c "wincmd l" "$LOCAL" "$MERGED" "$REMOTE" + check_unchanged + ;; gvimdiff) touch "$BACKUP" - "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE" + "$merge_tool_path" -c "wincmd l" -f "$LOCAL" "$MERGED" "$REMOTE" check_unchanged ;; xxdiff) @@ -267,7 +281,12 @@ merge_file () { if test "$status" -ne 0; then echo "merge of $MERGED failed" 1>&2 mv -- "$BACKUP" "$MERGED" - exit 1 + + if test "$merge_keep_temporaries" = "false"; then + cleanup_temp_files + fi + + return 1 fi if test "$merge_keep_backup" = "true"; then @@ -278,8 +297,11 @@ merge_file () { git add -- "$MERGED" cleanup_temp_files + return 0 } +prompt=$(git config --bool mergetool.prompt || echo true) + while test $# != 0 do case "$1" in @@ -295,6 +317,12 @@ do shift ;; esac ;; + -y|--no-prompt) + prompt=false + ;; + --prompt) + prompt=true + ;; --) shift break @@ -341,6 +369,22 @@ init_merge_tool_path() { fi } +prompt_after_failed_merge() { + while true; do + printf "Continue merging other unresolved paths (y/n) ? " + read ans + case "$ans" in + + [yY]*) + return 0 + ;; + + [nN]*) + return 1 + ;; + esac + done +} if test -z "$merge_tool"; then merge_tool=`git config merge.tool` @@ -353,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 @@ -389,6 +431,7 @@ else init_merge_tool_path "$merge_tool" merge_keep_backup="$(git config --bool merge.keepBackup || echo true)" + merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)" if test -z "$merge_tool_cmd" && ! type "$merge_tool_path" > /dev/null 2>&1; then echo "The merge tool $merge_tool is not available as '$merge_tool_path'" @@ -400,27 +443,44 @@ else fi fi +last_status=0 +rollup_status=0 if test $# -eq 0 ; then - files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` - if test -z "$files" ; then - echo "No files need merging" - exit 0 + files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` + if test -z "$files" ; then + echo "No files need merging" + exit 0 + fi + echo Merging the files: "$files" + git ls-files -u | + sed -e 's/^[^ ]* //' | + sort -u | + while IFS= read i + do + if test $last_status -ne 0; then + prompt_after_failed_merge < /dev/tty || exit 1 fi - echo Merging the files: "$files" - git ls-files -u | - sed -e 's/^[^ ]* //' | - sort -u | - while IFS= read i - do - printf "\n" - merge_file "$i" < /dev/tty > /dev/tty - done + printf "\n" + merge_file "$i" < /dev/tty > /dev/tty + last_status=$? + if test $last_status -ne 0; then + rollup_status=1 + fi + done else - while test $# -gt 0; do - printf "\n" - merge_file "$1" - shift - done + while test $# -gt 0; do + if test $last_status -ne 0; then + prompt_after_failed_merge || exit 1 + fi + printf "\n" + merge_file "$1" + last_status=$? + if test $last_status -ne 0; then + rollup_status=1 + fi + shift + done fi -exit 0 + +exit $rollup_status diff --git a/git-pull.sh b/git-pull.sh index 2c7f432dc0..c892059605 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -90,23 +90,31 @@ error_on_no_merge_candidates () { curr_branch=${curr_branch#refs/heads/} - echo "You asked me to pull without telling me which branch you" - echo "want to merge with, and 'branch.${curr_branch}.merge' in" - echo "your configuration file does not tell me either. Please" - echo "name which branch you want to merge on the command line and" - echo "try again (e.g. 'git pull <repository> <refspec>')." - echo "See git-pull(1) for details on the refspec." - echo - echo "If you often merge with the same branch, you may want to" - echo "configure the following variables in your configuration" - echo "file:" - echo - echo " branch.${curr_branch}.remote = <nickname>" - echo " branch.${curr_branch}.merge = <remote-ref>" - echo " remote.<nickname>.url = <url>" - echo " remote.<nickname>.fetch = <refspec>" - echo - echo "See git-config(1) for details." + if [ -z "$curr_branch" ]; then + echo "You are not currently on a branch, so I cannot use any" + echo "'branch.<branchname>.merge' in your configuration file." + echo "Please specify which branch you want to merge on the command" + echo "line and try again (e.g. 'git pull <repository> <refspec>')." + echo "See git-pull(1) for details." + else + echo "You asked me to pull without telling me which branch you" + echo "want to merge with, and 'branch.${curr_branch}.merge' in" + echo "your configuration file does not tell me either. Please" + echo "specify which branch you want to merge on the command line and" + echo "try again (e.g. 'git pull <repository> <refspec>')." + echo "See git-pull(1) for details." + echo + echo "If you often merge with the same branch, you may want to" + echo "configure the following variables in your configuration" + echo "file:" + echo + echo " branch.${curr_branch}.remote = <nickname>" + echo " branch.${curr_branch}.merge = <remote-ref>" + echo " remote.<nickname>.url = <url>" + echo " remote.<nickname>.fetch = <refspec>" + echo + echo "See git-config(1) for details." + fi exit 1 } @@ -171,6 +179,11 @@ case "$merge_head" in echo >&2 "Cannot merge multiple branches into empty head" exit 1 fi + if test true = "$rebase" + then + echo >&2 "Cannot rebase onto multiple branches" + exit 1 + fi ;; esac diff --git a/git-quiltimport.sh b/git-quiltimport.sh index cebaee1cc9..9a6ba2b987 100755 --- a/git-quiltimport.sh +++ b/git-quiltimport.sh @@ -63,7 +63,7 @@ tmp_info="$tmp_dir/info" commit=$(git rev-parse HEAD) mkdir $tmp_dir || exit 2 -while read patch_name level garbage +while read patch_name level garbage <&3 do case "$patch_name" in ''|'#'*) continue;; esac case "$level" in @@ -134,5 +134,5 @@ do commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) && git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4 fi -done <"$QUILT_PATCHES/series" +done 3<"$QUILT_PATCHES/series" rm -rf $tmp_dir || exit 5 diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 1ceb57ae83..3dc659dd58 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -27,6 +27,7 @@ continue continue rebasing process abort abort rebasing process and restore original branch skip skip current patch and continue rebasing process no-verify override pre-rebase hook from stopping the operation +root rebase all reachable commmits up to the root(s) " . git-sh-setup @@ -44,6 +45,7 @@ STRATEGY= ONTO= VERBOSE= OK_TO_SKIP_PRE_REBASE= +REBASE_ROOT= GIT_CHERRY_PICK_HELP=" After resolving the conflicts, mark the corrected paths with 'git add <paths>', and @@ -154,6 +156,11 @@ pick_one () { output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1" test -d "$REWRITTEN" && pick_one_preserving_merges "$@" && return + if test ! -z "$REBASE_ROOT" + then + output git cherry-pick "$@" + return + fi parent_sha1=$(git rev-parse --verify $sha1^) || die "Could not get the parent of $sha1" current_sha1=$(git rev-parse --verify HEAD) @@ -197,7 +204,11 @@ pick_one_preserving_merges () { # rewrite parents; if none were rewritten, we can fast-forward. new_parents= - pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)" + pend=" $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-)" + if test "$pend" = " " + then + pend=" root" + fi while [ "$pend" != "" ] do p=$(expr "$pend" : ' \([^ ]*\)') @@ -227,7 +238,9 @@ pick_one_preserving_merges () { if test -f "$DROPPED"/$p then fast_forward=f - pend=" $(cat "$DROPPED"/$p)$pend" + replacement="$(cat "$DROPPED"/$p)" + test -z "$replacement" && replacement=root + pend=" $replacement$pend" else new_parents="$new_parents $p" fi @@ -442,6 +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 -f "$DOTEST"/rebase-root && REBASE_ROOT=t } while test $# != 0 @@ -546,6 +560,9 @@ first and then run 'git rebase --continue' again." -i) # yeah, we know ;; + --root) + REBASE_ROOT=t + ;; --onto) shift ONTO=$(git rev-parse --verify "$1") || @@ -553,27 +570,38 @@ first and then run 'git rebase --continue' again." ;; --) shift - run_pre_rebase_hook ${1+"$@"} - test $# -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" git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first" + if test -z "$REBASE_ROOT" + then + UPSTREAM_ARG="$1" + UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" + test -z "$ONTO" && ONTO=$UPSTREAM + shift + else + UPSTREAM= + UPSTREAM_ARG=--root + test -z "$ONTO" && + die "You must specify --onto when using --root" + fi + run_pre_rebase_hook "$UPSTREAM_ARG" "$@" + comment_for_reflog start require_clean_work_tree - UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base" - test -z "$ONTO" && ONTO=$UPSTREAM - - if test ! -z "$2" + if test ! -z "$1" then - output git show-ref --verify --quiet "refs/heads/$2" || - die "Invalid branchname: $2" - output git checkout "$2" || - die "Could not checkout $2" + output git show-ref --verify --quiet "refs/heads/$1" || + die "Invalid branchname: $1" + output git checkout "$1" || + die "Could not checkout $1" fi HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?" @@ -584,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 @@ -597,12 +630,19 @@ first and then run 'git rebase --continue' again." # This ensures that commits on merged, but otherwise # unrelated side branches are left alone. (Think "X" # in the man page's example.) - mkdir "$REWRITTEN" && - for c in $(git merge-base --all $HEAD $UPSTREAM) - do - echo $ONTO > "$REWRITTEN"/$c || + if test -z "$REBASE_ROOT" + then + mkdir "$REWRITTEN" && + for c in $(git merge-base --all $HEAD $UPSTREAM) + do + echo $ONTO > "$REWRITTEN"/$c || + die "Could not init rewritten commits" + done + else + mkdir "$REWRITTEN" && + echo $ONTO > "$REWRITTEN"/root || die "Could not init rewritten commits" - done + fi # No cherry-pick because our first pass is to determine # parents to rewrite and skipping dropped commits would # prematurely end our probe @@ -612,12 +652,21 @@ first and then run 'git rebase --continue' again." MERGES_OPTION="--no-merges --cherry-pick" fi - SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) SHORTHEAD=$(git rev-parse --short $HEAD) SHORTONTO=$(git rev-parse --short $ONTO) + if test -z "$REBASE_ROOT" + # this is now equivalent to ! -z "$UPSTREAM" + then + SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM) + REVISIONS=$UPSTREAM...$HEAD + SHORTREVISIONS=$SHORTUPSTREAM..$SHORTHEAD + else + REVISIONS=$ONTO...$HEAD + SHORTREVISIONS=$SHORTHEAD + fi git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \ --abbrev=7 --reverse --left-right --topo-order \ - $UPSTREAM...$HEAD | \ + $REVISIONS | \ sed -n "s/^>//p" | while read shortsha1 rest do if test t != "$PRESERVE_MERGES" @@ -625,14 +674,19 @@ first and then run 'git rebase --continue' again." echo "pick $shortsha1 $rest" >> "$TODO" else sha1=$(git rev-parse $shortsha1) - preserve=t - for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-) - do - if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \) - then - preserve=f - fi - done + if test -z "$REBASE_ROOT" + then + preserve=t + for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -s -f2-) + do + if test -f "$REWRITTEN"/$p -a \( $p != $UPSTREAM -o $sha1 = $first_after_upstream \) + then + preserve=f + fi + done + else + preserve=f + fi if test f = "$preserve" then touch "$REWRITTEN"/$sha1 @@ -646,11 +700,11 @@ first and then run 'git rebase --continue' again." then mkdir "$DROPPED" # Save all non-cherry-picked changes - git rev-list $UPSTREAM...$HEAD --left-right --cherry-pick | \ + git rev-list $REVISIONS --left-right --cherry-pick | \ sed -n "s/^>//p" > "$DOTEST"/not-cherry-picks # Now all commits and note which ones are missing in # not-cherry-picks and hence being dropped - git rev-list $UPSTREAM..$HEAD | + git rev-list $REVISIONS | while read rev do if test -f "$REWRITTEN"/$rev -a "$(grep "$rev" "$DOTEST"/not-cherry-picks)" = "" @@ -659,17 +713,18 @@ first and then run 'git rebase --continue' again." # not worthwhile, we don't want to track its multiple heads, # just the history of its first-parent for others that will # be rebasing on top of it - git rev-list --parents -1 $rev | cut -d' ' -f2 > "$DROPPED"/$rev + git rev-list --parents -1 $rev | cut -d' ' -s -f2 > "$DROPPED"/$rev short=$(git rev-list -1 --abbrev-commit --abbrev=7 $rev) grep -v "^[a-z][a-z]* $short" <"$TODO" > "${TODO}2" ; mv "${TODO}2" "$TODO" rm "$REWRITTEN"/$rev fi done fi + test -s "$TODO" || echo noop >> "$TODO" cat >> "$TODO" << EOF -# Rebase $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO +# Rebase $SHORTREVISIONS onto $SHORTONTO # # Commands: # p, pick = use commit diff --git a/git-rebase.sh b/git-rebase.sh index ebd4df3a0e..368c0ef434 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]' +USAGE='[--interactive | -i] [-v] [--onto <newbase>] [<upstream>|--root] [<branch>]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> @@ -47,6 +47,7 @@ dotest="$GIT_DIR"/rebase-merge prec=4 verbose= git_am_opt= +rebase_root= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -297,6 +298,9 @@ do -C*) git_am_opt="$git_am_opt $1" ;; + --root) + rebase_root=t + ;; -*) usage ;; @@ -306,6 +310,7 @@ do esac shift done +test $# -gt 2 && usage # Make sure we do not have $GIT_DIR/rebase-apply if test -z "$do_merge" @@ -344,17 +349,29 @@ case "$diff" in ;; esac -# The upstream head must be given. Make sure it is valid. -upstream_name="$1" -upstream=`git rev-parse --verify "${upstream_name}^0"` || - die "invalid upstream $upstream_name" +if test -z "$rebase_root" +then + # The upstream head must be given. Make sure it is valid. + upstream_name="$1" + shift + upstream=`git rev-parse --verify "${upstream_name}^0"` || + die "invalid upstream $upstream_name" + unset root_flag + upstream_arg="$upstream_name" +else + test -z "$newbase" && die "--root must be used with --onto" + unset upstream_name + unset upstream + root_flag="--root" + upstream_arg="$root_flag" +fi # Make sure the branch to rebase onto is valid. onto_name=${newbase-"$upstream_name"} onto=$(git rev-parse --verify "${onto_name}^0") || exit # If a hook exists, give it a chance to interrupt -run_pre_rebase_hook ${1+"$@"} +run_pre_rebase_hook "$upstream_arg" "$@" # If the branch to rebase is given, that is the branch we will rebase # $branch_name -- branch being rebased, or HEAD (already detached) @@ -362,16 +379,16 @@ run_pre_rebase_hook ${1+"$@"} # $head_name -- refs/heads/<that-branch> or "detached HEAD" switch_to= case "$#" in -2) +1) # Is it "rebase other $branchname" or "rebase other $commit"? - branch_name="$2" - switch_to="$2" + branch_name="$1" + switch_to="$1" - if git show-ref --verify --quiet -- "refs/heads/$2" && - branch=$(git rev-parse -q --verify "refs/heads/$2") + if git show-ref --verify --quiet -- "refs/heads/$1" && + branch=$(git rev-parse -q --verify "refs/heads/$1") then - head_name="refs/heads/$2" - elif branch=$(git rev-parse -q --verify "$2") + head_name="refs/heads/$1" + elif branch=$(git rev-parse -q --verify "$1") then head_name="detached HEAD" else @@ -393,7 +410,8 @@ case "$#" in esac orig_head=$branch -# Now we are rebasing commits $upstream..$branch on top of $onto +# Now we are rebasing commits $upstream..$branch (or with --root, +# everything leading up to $branch) on top of $onto # Check if we are already based on $onto with linear history, # but this should be done only when upstream and onto are the same. @@ -429,10 +447,17 @@ then exit 0 fi +if test -n "$rebase_root" +then + revisions="$onto..$orig_head" +else + revisions="$upstream..$orig_head" +fi + if test -z "$do_merge" then git format-patch -k --stdout --full-index --ignore-if-in-upstream \ - "$upstream..$orig_head" | + $root_flag "$revisions" | git am $git_am_opt --rebasing --resolvemsg="$RESOLVEMSG" && move_to_original_branch ret=$? @@ -455,7 +480,7 @@ echo "$orig_head" > "$dotest/orig-head" echo "$head_name" > "$dotest/head-name" msgnum=0 -for cmt in `git rev-list --reverse --no-merges "$upstream..$orig_head"` +for cmt in `git rev-list --reverse --no-merges "$revisions"` do msgnum=$(($msgnum + 1)) echo "$cmt" > "$dotest/cmt.$msgnum" diff --git a/git-send-email.perl b/git-send-email.perl index 77ca8fe880..09fe3d95c4 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -23,7 +23,7 @@ use Getopt::Long; use Text::ParseWords; use Data::Dumper; use Term::ANSIColor; -use File::Temp qw/ tempdir /; +use File::Temp qw/ tempdir tempfile /; use Error qw(:try); use Git; @@ -68,9 +68,8 @@ git send-email [options] <file | directory | rev-list options > Automating: --identity <str> * Use the sendemail.<id> options. --cc-cmd <str> * Email Cc: via `<str> \$patch_path` - --suppress-cc <str> * author, self, sob, cccmd, all. - --[no-]signed-off-by-cc * Send to Cc: and Signed-off-by: - addresses. Default on. + --suppress-cc <str> * author, self, sob, cc, cccmd, body, bodycc, all. + --[no-]signed-off-by-cc * Send to Signed-off-by: addresses. Default on. --[no-]suppress-from * Send to self. Default off. --[no-]chain-reply-to * Chain In-Reply-To: fields. Default on. --[no-]thread * Use In-Reply-To: field. Default on. @@ -126,6 +125,7 @@ sub format_2822_time { } my $have_email_valid = eval { require Email::Valid; 1 }; +my $have_mail_address = eval { require Mail::Address; 1 }; my $smtp; my $auth; @@ -156,7 +156,7 @@ if ($@) { # Behavior modification variables my ($quiet, $dry_run) = (0, 0); my $format_patch; -my $compose_filename = $repo->repo_path() . "/.gitsendemail.msg.$$"; +my $compose_filename; # Handle interactive edition of files. my $multiedit; @@ -219,11 +219,13 @@ sub signal_handler { system "stty echo"; # tmp files from --compose - if (-e $compose_filename) { - print "'$compose_filename' contains an intermediate version of the email you were composing.\n"; - } - if (-e ($compose_filename . ".final")) { - print "'$compose_filename.final' contains the composed email.\n" + if (defined $compose_filename) { + if (-e $compose_filename) { + print "'$compose_filename' contains an intermediate version of the email you were composing.\n"; + } + if (-e ($compose_filename . ".final")) { + print "'$compose_filename.final' contains the composed email.\n" + } } exit; @@ -267,6 +269,9 @@ unless ($rc) { usage(); } +die "Cannot run git format-patch from outside a repository\n" + if $format_patch and not $repo; + # Now, let's fill any that aren't set in with defaults: sub read_config { @@ -318,13 +323,13 @@ my(%suppress_cc); if (@suppress_cc) { foreach my $entry (@suppress_cc) { die "Unknown --suppress-cc field: '$entry'\n" - unless $entry =~ /^(all|cccmd|cc|author|self|sob)$/; + unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/; $suppress_cc{$entry} = 1; } } if ($suppress_cc{'all'}) { - foreach my $entry (qw (ccmd cc author self sob)) { + foreach my $entry (qw (ccmd cc author self sob body bodycc)) { $suppress_cc{$entry} = 1; } delete $suppress_cc{'all'}; @@ -334,6 +339,13 @@ if ($suppress_cc{'all'}) { $suppress_cc{'self'} = $suppress_from if defined $suppress_from; $suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc; +if ($suppress_cc{'body'}) { + foreach my $entry (qw (sob bodycc)) { + $suppress_cc{$entry} = 1; + } + delete $suppress_cc{'body'}; +} + # Debugging, print out the suppressions. if (0) { print "suppressions:\n"; @@ -360,6 +372,14 @@ foreach my $entry (@bcclist) { die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/; } +sub parse_address_line { + if ($have_mail_address) { + return map { $_->format } Mail::Address->parse($_[0]); + } else { + return split_addrs($_[0]); + } +} + sub split_addrs { return quotewords('\s*,\s*', 1, @_); } @@ -404,6 +424,7 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { # returns 1 if the conflict must be solved using it as a format-patch argument sub check_file_rev_conflict($) { + return unless $repo; my $f = shift; try { $repo->command('rev-parse', '--verify', '--quiet', $f); @@ -445,6 +466,8 @@ while (defined(my $f = shift @ARGV)) { } if (@rev_list_opts) { + die "Cannot run git format-patch from outside a repository\n" + unless $repo; push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts); } @@ -481,6 +504,9 @@ sub get_patch_subject($) { if ($compose) { # Note that this does not need to be secure, but we will make a small # effort to have it be unique + $compose_filename = ($repo ? + tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) : + tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1]; open(C,">",$compose_filename) or die "Failed to open for writing $compose_filename: $!"; @@ -593,7 +619,7 @@ if (!@to) { } my $to = $_; - push @to, split_addrs($to); + push @to, parse_address_line($to); $prompting++; } @@ -795,7 +821,7 @@ Date: $date Message-Id: $message_id X-Mailer: git-send-email $gitversion "; - if ($thread && $reply_to) { + if ($reply_to) { $header .= "In-Reply-To: $reply_to\n"; $header .= "References: $references\n"; @@ -920,88 +946,102 @@ foreach my $t (@files) { @cc = @initial_cc; @xh = (); my $input_format = undef; - my $header_done = 0; + my @header = (); $message = ""; + # First unfold multiline header fields while(<F>) { - if (!$header_done) { - if (/^From /) { - $input_format = 'mbox'; - next; + last if /^\s*$/; + if (/^\s+\S/ and @header) { + chomp($header[$#header]); + s/^\s+/ /; + $header[$#header] .= $_; + } else { + push(@header, $_); + } + } + # Now parse the header + foreach(@header) { + if (/^From /) { + $input_format = 'mbox'; + next; + } + chomp; + if (!defined $input_format && /^[-A-Za-z]+:\s/) { + $input_format = 'mbox'; + } + + if (defined $input_format && $input_format eq 'mbox') { + if (/^Subject:\s+(.*)$/) { + $subject = $1; } - chomp; - if (!defined $input_format && /^[-A-Za-z]+:\s/) { - $input_format = 'mbox'; + elsif (/^From:\s+(.*)$/) { + ($author, $author_encoding) = unquote_rfc2047($1); + next if $suppress_cc{'author'}; + next if $suppress_cc{'self'} and $author eq $sender; + printf("(mbox) Adding cc: %s from line '%s'\n", + $1, $_) unless $quiet; + push @cc, $1; } - - if (defined $input_format && $input_format eq 'mbox') { - if (/^Subject:\s+(.*)$/) { - $subject = $1; - - } elsif (/^(Cc|From):\s+(.*)$/) { - if (unquote_rfc2047($2) eq $sender) { + elsif (/^Cc:\s+(.*)$/) { + foreach my $addr (parse_address_line($1)) { + if (unquote_rfc2047($addr) eq $sender) { next if ($suppress_cc{'self'}); - } - elsif ($1 eq 'From') { - ($author, $author_encoding) - = unquote_rfc2047($2); - next if ($suppress_cc{'author'}); } else { next if ($suppress_cc{'cc'}); } printf("(mbox) Adding cc: %s from line '%s'\n", - $2, $_) unless $quiet; - push @cc, $2; - } - elsif (/^Content-type:/i) { - $has_content_type = 1; - if (/charset="?([^ "]+)/) { - $body_encoding = $1; - } - push @xh, $_; - } - elsif (/^Message-Id: (.*)/i) { - $message_id = $1; + $addr, $_) unless $quiet; + push @cc, $addr; } - elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { - push @xh, $_; - } - - } else { - # In the traditional - # "send lots of email" format, - # line 1 = cc - # line 2 = subject - # So let's support that, too. - $input_format = 'lots'; - if (@cc == 0 && !$suppress_cc{'cc'}) { - printf("(non-mbox) Adding cc: %s from line '%s'\n", - $_, $_) unless $quiet; - - push @cc, $_; - - } elsif (!defined $subject) { - $subject = $_; + } + elsif (/^Content-type:/i) { + $has_content_type = 1; + if (/charset="?([^ "]+)/) { + $body_encoding = $1; } + push @xh, $_; } - - # A whitespace line will terminate the headers - if (m/^\s*$/) { - $header_done = 1; + elsif (/^Message-Id: (.*)/i) { + $message_id = $1; } + elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) { + push @xh, $_; + } + } else { - $message .= $_; - if (/^(Signed-off-by|Cc): (.*)$/i) { - next if ($suppress_cc{'sob'}); - chomp; - my $c = $2; - chomp $c; - next if ($c eq $sender and $suppress_cc{'self'}); - push @cc, $c; - printf("(sob) Adding cc: %s from line '%s'\n", - $c, $_) unless $quiet; + # In the traditional + # "send lots of email" format, + # line 1 = cc + # line 2 = subject + # So let's support that, too. + $input_format = 'lots'; + if (@cc == 0 && !$suppress_cc{'cc'}) { + printf("(non-mbox) Adding cc: %s from line '%s'\n", + $_, $_) unless $quiet; + push @cc, $_; + } elsif (!defined $subject) { + $subject = $_; } } } + # Now parse the message body + while(<F>) { + $message .= $_; + if (/^(Signed-off-by|Cc): (.*)$/i) { + chomp; + my ($what, $c) = ($1, $2); + chomp $c; + if ($c eq $sender) { + next if ($suppress_cc{'self'}); + } else { + next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i; + next if $suppress_cc{'bodycc'} and $what =~ /Cc/i; + } + push @cc, $c; + printf("(body) Adding cc: %s from line '%s'\n", + $c, $_) unless $quiet; + } + } close F; if (defined $cc_cmd && !$suppress_cc{'cccmd'}) { @@ -1020,7 +1060,7 @@ foreach my $t (@files) { or die "(cc-cmd) failed to close pipe to '$cc_cmd'"; } - if (defined $author) { + if (defined $author and $author ne $sender) { $message = "From: $author\n\n$message"; if (defined $author_encoding) { if ($has_content_type) { diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 2142308bcc..838233926f 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -85,27 +85,14 @@ cd_to_toplevel () { cdup=$(git rev-parse --show-cdup) if test ! -z "$cdup" then - case "$cdup" in - /*) - # Not quite the same as if we did "cd -P '$cdup'" when - # $cdup contains ".." after symlink path components. - # Don't fix that case at least until Git switches to - # "cd -P" across the board. - phys="$cdup" - ;; - ..|../*|*/..|*/../*) - # Interpret $cdup relative to the physical, not logical, cwd. - # Probably /bin/pwd is more portable than passing -P to cd or pwd. - phys="$(unset PWD; /bin/pwd)/$cdup" - ;; - *) - # There's no "..", so no need to make things absolute. - phys="$cdup" - ;; - esac - - cd "$phys" || { - echo >&2 "Cannot chdir to $phys, the toplevel of the working tree" + # The "-P" option says to follow "physical" directory + # structure instead of following symbolic links. When cdup is + # "../", this means following the ".." entry in the current + # directory instead textually removing a symlink path element + # from the PWD shell variable. The "-P" behavior is more + # consistent with the C-style chdir used by most of Git. + cd -P "$cdup" || { + echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree" exit 1 } fi diff --git a/git-submodule.sh b/git-submodule.sh index 2f47e065fe..7c2e060ae7 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -5,7 +5,7 @@ # Copyright (c) 2007 Lars Hjemli USAGE="[--quiet] [--cached] \ -[add <repo> [-b branch] <path>]|[status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \ +[add [-b branch] <repo> <path>]|[status|init|update [-i|--init] [-N|--no-fetch]|summary [-n|--summary-limit <n>] [<commit>]] \ [--] [<path>...]|[foreach <command>]|[sync [--] [<path>...]]" OPTIONS_SPEC= . git-sh-setup @@ -16,6 +16,7 @@ command= branch= quiet= cached= +nofetch= # # print stuff on stdout unless -q was specified @@ -59,7 +60,7 @@ resolve_relative_url () # module_list() { - git ls-files --stage -- "$@" | grep '^160000 ' + git ls-files --error-unmatch --stage -- "$@" | grep '^160000 ' } # @@ -166,9 +167,18 @@ cmd_add() ;; esac - # strip trailing slashes from path - path=$(echo "$path" | sed -e 's|/*$||') - + # normalize path: + # multiple //; leading ./; /./; /../; trailing / + path=$(printf '%s/\n' "$path" | + sed -e ' + s|//*|/|g + s|^\(\./\)*|| + s|/\./|/|g + :start + s|\([^/]*\)/\.\./|| + tstart + s|/*$|| + ') git ls-files --error-unmatch "$path" > /dev/null 2>&1 && die "'$path' already exists in the index" @@ -300,6 +310,10 @@ cmd_update() shift cmd_init "$@" || return ;; + -N|--no-fetch) + shift + nofetch=1 + ;; --) shift break @@ -345,8 +359,16 @@ cmd_update() then force="-f" fi - (unset GIT_DIR; cd "$path" && git-fetch && - git-checkout $force -q "$sha1") || + + if test -z "$nofetch" + then + (unset GIT_DIR; cd "$path" && + git-fetch) || + die "Unable to fetch in submodule path '$path'" + fi + + (unset GIT_DIR; cd "$path" && + git-checkout $force -q "$sha1") || die "Unable to checkout '$sha1' in submodule path '$path'" say "Submodule path '$path': checked out '$sha1'" diff --git a/git-svn.perl b/git-svn.perl index ad01e182df..3b524207ff 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, @@ -84,6 +85,7 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, \$Git::SVN::_repack_flags, 'use-log-author' => \$Git::SVN::_use_log_author, 'add-author-from' => \$Git::SVN::_add_author_from, + 'localtime' => \$Git::SVN::_localtime, %remote_opts ); my ($_trunk, $_tags, $_branches, $_stdlayout); @@ -142,7 +144,7 @@ my %cmd = ( 'dry-run|n' => \$_dry_run } ], 'set-tree' => [ \&cmd_set_tree, "Set an SVN repository to a git tree-ish", - { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ], + { 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ], 'create-ignore' => [ \&cmd_create_ignore, 'Create a .gitignore per svn:ignore', { 'revision|r=i' => \$_revision @@ -436,7 +438,17 @@ sub cmd_dcommit { die "Unable to determine upstream SVN information from ", "$head history.\nPerhaps the repository is empty."; } - $url = defined $_commit_url ? $_commit_url : $gs->full_url; + + if (defined $_commit_url) { + $url = $_commit_url; + } else { + $url = eval { command_oneline('config', '--get', + "svn-remote.$gs->{repo_id}.commiturl") }; + if (!$url) { + $url = $gs->full_url + } + } + my $last_rev = $_revision if defined $_revision; if ($url) { print "Committing to $url ...\n"; @@ -668,7 +680,11 @@ sub cmd_create_ignore { $gs->prop_walk($gs->{path}, $r, sub { my ($gs, $path, $props) = @_; # $path is of the form /path/to/dir/ - my $ignore = '.' . $path . '.gitignore'; + $path = '.' . $path; + # SVN can have attributes on empty directories, + # which git won't track + mkpath([$path]) unless -d $path; + my $ignore = $path . '.gitignore'; my $s = $props->{'svn:ignore'} or return; open(GITIGNORE, '>', $ignore) or fatal("Failed to open `$ignore' for writing: $!"); @@ -911,7 +927,8 @@ sub cmd_info { if ($@) { $result .= "Repository Root: (offline)\n"; } - $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A"; + $result .= "Repository UUID: $uuid\n" unless $diff_status eq "A" && + ($SVN::Core::VERSION le '1.5.4' || $file_type ne "dir"); $result .= "Revision: " . ($diff_status eq "A" ? 0 : $rev) . "\n"; $result .= "Node Kind: " . @@ -1364,7 +1381,7 @@ use constant rev_map_fmt => 'NH40'; use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent $_repack $_repack_flags $_use_svm_props $_head $_use_svnsync_props $no_reuse_existing $_minimize_url - $_use_log_author $_add_author_from/; + $_use_log_author $_add_author_from $_localtime/; use Carp qw/croak/; use File::Path qw/mkpath/; use File::Copy qw/copy/; @@ -1690,6 +1707,7 @@ sub find_by_url { # repos_root and, path are optional my $prefix = ''; if ($rwr) { $z = $rwr; + remove_username($z); } elsif (defined $svm) { $z = $svm->{source}; $prefix = $svm->{replace}; @@ -2386,22 +2404,8 @@ sub find_parent_branch { print STDERR "Found possible branch point: ", "$new_url => ", $self->full_url, ", $r\n"; $branch_from =~ s#^/##; - my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from); - unless ($gs) { - my $ref_id = $self->{ref_id}; - $ref_id =~ s/\@\d+$//; - $ref_id .= "\@$r"; - # just grow a tail if we're not unique enough :x - $ref_id .= '-' while find_ref($ref_id); - print STDERR "Initializing parent: $ref_id\n"; - my ($u, $p, $repo_id) = ($new_url, '', $ref_id); - if ($u =~ s#^\Q$url\E(/|$)##) { - $p = $u; - $u = $url; - $repo_id = $self->{repo_id}; - } - $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1); - } + my $gs = $self->other_gs($new_url, $url, $repos_root, + $branch_from, $r, $self->{ref_id}); my ($r0, $parent) = $gs->find_rev_before($r, 1); { my ($base, $head); @@ -2427,8 +2431,9 @@ sub find_parent_branch { # do_switch works with svn/trunk >= r22312, but that # is not included with SVN 1.4.3 (the latest version # at the moment), so we can't rely on it + $self->{last_rev} = $r0; $self->{last_commit} = $parent; - $ed = SVN::Git::Fetcher->new($self); + $ed = SVN::Git::Fetcher->new($self, $gs->{path}); $gs->ra->gs_do_switch($r0, $rev, $gs, $self->full_url, $ed) or die "SVN connection failed somewhere...\n"; @@ -2526,12 +2531,83 @@ sub get_untracked { \@out; } +# parse_svn_date(DATE) +# -------------------- +# Given a date (in UTC) from Subversion, return a string in the format +# "<TZ Offset> <local date/time>" that Git will use. +# +# By default the parsed date will be in UTC; if $Git::SVN::_localtime +# is true we'll convert it to the local timezone instead. sub parse_svn_date { my $date = shift || return '+0000 1970-01-01 00:00:00'; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T - (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or + (\d\d)\:(\d\d)\:(\d\d)\.\d*Z$/x) or croak "Unable to parse date: $date\n"; - "+0000 $Y-$m-$d $H:$M:$S"; + my $parsed_date; # Set next. + + if ($Git::SVN::_localtime) { + # Translate the Subversion datetime to an epoch time. + # Begin by switching ourselves to $date's timezone, UTC. + my $old_env_TZ = $ENV{TZ}; + $ENV{TZ} = 'UTC'; + + my $epoch_in_UTC = + POSIX::strftime('%s', $S, $M, $H, $d, $m - 1, $Y - 1900); + + # Determine our local timezone (including DST) at the + # time of $epoch_in_UTC. $Git::SVN::Log::TZ stored the + # value of TZ, if any, at the time we were run. + if (defined $Git::SVN::Log::TZ) { + $ENV{TZ} = $Git::SVN::Log::TZ; + } else { + delete $ENV{TZ}; + } + + my $our_TZ = + POSIX::strftime('%Z', $S, $M, $H, $d, $m - 1, $Y - 1900); + + # This converts $epoch_in_UTC into our local timezone. + my ($sec, $min, $hour, $mday, $mon, $year, + $wday, $yday, $isdst) = localtime($epoch_in_UTC); + + $parsed_date = sprintf('%s %04d-%02d-%02d %02d:%02d:%02d', + $our_TZ, $year + 1900, $mon + 1, + $mday, $hour, $min, $sec); + + # Reset us to the timezone in effect when we entered + # this routine. + if (defined $old_env_TZ) { + $ENV{TZ} = $old_env_TZ; + } else { + delete $ENV{TZ}; + } + } else { + $parsed_date = "+0000 $Y-$m-$d $H:$M:$S"; + } + + return $parsed_date; +} + +sub other_gs { + my ($self, $new_url, $url, $repos_root, + $branch_from, $r, $old_ref_id) = @_; + my $gs = Git::SVN->find_by_url($new_url, $repos_root, $branch_from); + unless ($gs) { + my $ref_id = $old_ref_id; + $ref_id =~ s/\@\d+$//; + $ref_id .= "\@$r"; + # just grow a tail if we're not unique enough :x + $ref_id .= '-' while find_ref($ref_id); + print STDERR "Initializing parent: $ref_id\n"; + my ($u, $p, $repo_id) = ($new_url, '', $ref_id); + if ($u =~ s#^\Q$url\E(/|$)##) { + $p = $u; + $u = $url; + $repo_id = $self->{repo_id}; + } + $gs = Git::SVN->init($u, $p, $repo_id, $ref_id, 1); + } + $gs } sub check_author { @@ -3194,13 +3270,18 @@ 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 { - my ($class, $git_svn) = @_; + my ($class, $git_svn, $switch_path) = @_; my $self = SVN::Delta::Editor->new; bless $self, $class; - $self->{c} = $git_svn->{last_commit} if exists $git_svn->{last_commit}; + if (exists $git_svn->{last_commit}) { + $self->{c} = $git_svn->{last_commit}; + $self->{empty_symlinks} = + _mark_empty_symlinks($git_svn, $switch_path); + } $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -3210,6 +3291,68 @@ sub new { $self; } +# this uses the Ra object, so it must be called before do_{switch,update}, +# not inside them (when the Git::SVN::Fetcher object is passed) to +# do_{switch,update} +sub _mark_empty_symlinks { + my ($git_svn, $switch_path) = @_; + my $bool = Git::config_bool('svn.brokenSymlinkWorkaround'); + return {} if (!defined($bool)) || (defined($bool) && ! $bool); + + my %ret; + my ($rev, $cmt) = $git_svn->last_rev_commit; + return {} unless ($rev && $cmt); + + # allow the warning to be printed for each revision we fetch to + # ensure the user sees it. The user can also disable the workaround + # on the repository even while git svn is running and the next + # revision fetched will skip this expensive function. + my $printed_warning; + chomp(my $empty_blob = `git hash-object -t blob --stdin < /dev/null`); + my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r -z/, $cmt); + local $/ = "\0"; + my $pfx = defined($switch_path) ? $switch_path : $git_svn->{path}; + $pfx .= '/' if length($pfx); + while (<$ls>) { + chomp; + s/\A100644 blob $empty_blob\t//o or next; + unless ($printed_warning) { + print STDERR "Scanning for empty symlinks, ", + "this may take a while if you have ", + "many empty files\n", + "You may disable this with `", + "git config svn.brokenSymlinkWorkaround ", + "false'.\n", + "This may be done in a different ", + "terminal without restarting ", + "git svn\n"; + $printed_warning = 1; + } + my $path = $_; + my (undef, $props) = + $git_svn->ra->get_file($pfx.$path, $rev, undef); + if ($props->{'svn:special'}) { + $ret{$path} = 1; + } + } + command_close_pipe($ls, $ctx); + \%ret; +} + +# returns true if a given path is inside a ".git" directory +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; @@ -3235,20 +3378,24 @@ sub git_path { sub delete_entry { my ($self, $path, $rev, $pb) = @_; + return undef if is_path_ignored($path); my $gpath = $self->git_path($path); return undef if ($gpath eq ''); # remove entire directories. - if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) { + my ($tree) = (command('ls-tree', '-z', $self->{c}, "./$gpath") + =~ /\A040000 tree ([a-f\d]{40})\t\Q$gpath\E\0/); + if ($tree) { my ($ls, $ctx) = command_output_pipe(qw/ls-tree -r --name-only -z/, - $self->{c}, '--', $gpath); + $tree); local $/ = "\0"; while (<$ls>) { chomp; - $self->{gii}->remove($_); - print "\tD\t$_\n" unless $::_q; + my $rmpath = "$gpath/$_"; + $self->{gii}->remove($rmpath); + print "\tD\t$rmpath\n" unless $::_q; } print "\tD\t$gpath/\n" unless $::_q; command_close_pipe($ls, $ctx); @@ -3262,26 +3409,40 @@ sub delete_entry { sub open_file { my ($self, $path, $pb, $rev) = @_; + my ($mode, $blob); + + goto out if is_path_ignored($path); + my $gpath = $self->git_path($path); - my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath) - =~ /^(\d{6}) blob ([a-f\d]{40})\t/); + ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath") + =~ /\A(\d{6}) blob ([a-f\d]{40})\t\Q$gpath\E\0/); unless (defined $mode && defined $blob) { die "$path was not found in commit $self->{c} (r$rev)\n"; } + if ($mode eq '100644' && $self->{empty_symlinks}->{$path}) { + $mode = '120000'; + } +out: { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob, pool => SVN::Pool->new, action => 'M' }; } sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; - my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); - delete $self->{empty}->{$dir}; - { path => $path, mode_a => 100644, mode_b => 100644, + my $mode; + + if (!is_path_ignored($path)) { + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; + $mode = '100644'; + } + { path => $path, mode_a => $mode, mode_b => $mode, pool => SVN::Pool->new, action => 'A' }; } sub add_directory { my ($self, $path, $cp_path, $cp_rev) = @_; + 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 @@ -3299,11 +3460,13 @@ sub add_directory { my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); delete $self->{empty}->{$dir}; $self->{empty}->{$path} = 1; +out: { path => $path }; } sub change_dir_prop { my ($self, $db, $prop, $value) = @_; + return undef if is_path_ignored($db->{path}); $self->{dir_prop}->{$db->{path}} ||= {}; $self->{dir_prop}->{$db->{path}}->{$prop} = $value; undef; @@ -3311,6 +3474,7 @@ sub change_dir_prop { sub absent_directory { my ($self, $path, $pb) = @_; + return undef if is_path_ignored($path); $self->{absent_dir}->{$pb->{path}} ||= []; push @{$self->{absent_dir}->{$pb->{path}}}, $path; undef; @@ -3318,6 +3482,7 @@ sub absent_directory { sub absent_file { my ($self, $path, $pb) = @_; + return undef if is_path_ignored($path); $self->{absent_file}->{$pb->{path}} ||= []; push @{$self->{absent_file}->{$pb->{path}}}, $path; undef; @@ -3325,6 +3490,7 @@ sub absent_file { sub change_file_prop { my ($self, $fb, $prop, $value) = @_; + 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; @@ -3340,22 +3506,43 @@ sub change_file_prop { sub apply_textdelta { my ($self, $fb, $exp) = @_; + 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 open my $dup, '<&', $fh or croak $!; my $base = $::_repository->temp_acquire('git_blob'); + if ($fb->{blob}) { - print $base 'link ' if ($fb->{mode_a} == 120000); - my $size = $::_repository->cat_blob($fb->{blob}, $base); + my ($base_is_link, $size); + + if ($fb->{mode_a} eq '120000' && + ! $self->{empty_symlinks}->{$fb->{path}}) { + print $base 'link ' or die "print $!\n"; + $base_is_link = 1; + } + retry: + $size = $::_repository->cat_blob($fb->{blob}, $base); die "Failed to read object $fb->{blob}" if ($size < 0); if (defined $exp) { seek $base, 0, 0 or croak $!; my $got = ::md5sum($base); - die "Checksum mismatch: $fb->{path} $fb->{blob}\n", - "expected: $exp\n", - " got: $got\n" if ($got ne $exp); + if ($got ne $exp) { + my $err = "Checksum mismatch: ". + "$fb->{path} $fb->{blob}\n" . + "expected: $exp\n" . + " got: $got\n"; + if ($base_is_link) { + warn $err, + "Retrying... (possibly ", + "a bad symlink from SVN)\n"; + $::_repository->temp_reset($base); + $base_is_link = 0; + goto retry; + } + die $err; + } } } seek $base, 0, 0 or croak $!; @@ -3366,6 +3553,8 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; + return undef if is_path_ignored($fb->{path}); + my $hash; my $path = $self->git_path($fb->{path}); if (my $fh = $fb->{fh}) { @@ -3379,11 +3568,19 @@ sub close_file { } if ($fb->{mode_b} == 120000) { sysseek($fh, 0, 0) or croak $!; - sysread($fh, my $buf, 5) == 5 or croak $!; + my $rd = sysread($fh, my $buf, 5); - unless ($buf eq 'link ') { + if (!defined $rd) { + croak "sysread: $!\n"; + } elsif ($rd == 0) { warn "$path has mode 120000", - " but is not a link\n"; + " but it points to nothing\n", + "converting to an empty file with mode", + " 100644\n"; + $fb->{mode_b} = '100644'; + } elsif ($buf ne 'link ') { + warn "$path has mode 120000", + " but is not a link\n"; } else { my $tmp_fh = $::_repository->temp_acquire( 'svn_hash'); @@ -3883,7 +4080,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; @@ -4019,10 +4217,23 @@ sub DESTROY { # do not call the real DESTROY since we store ourselves in $RA } +# get_log(paths, start, end, limit, +# discover_changed_paths, strict_node_history, receiver) sub get_log { my ($self, @args) = @_; my $pool = SVN::Pool->new; - splice(@args, 3, 1) if ($SVN::Core::VERSION le '1.2.0'); + + # the limit parameter was not supported in SVN 1.1.x, so we + # drop it. Therefore, the receiver callback passed to it + # is made aware of this limitation by being wrapped if + # the limit passed to is being wrapped. + if ($SVN::Core::VERSION le '1.2.0') { + my $limit = splice(@args, 3, 1); + if ($limit > 0) { + my $receiver = pop @args; + push(@args, sub { &$receiver(@_) if (--$limit >= 0) }); + } + } my $ret = $self->SUPER::get_log(@args, $pool); $pool->clear; $ret; @@ -4185,6 +4396,9 @@ sub gs_fetch_loop_common { } $self->get_log([$longest_path], $min, $max, 0, 1, 1, sub { $revs{$_[1]} = _cb(@_) }); + if ($err) { + print "Checked through r$max\r"; + } if ($err && $max >= $head) { print STDERR "Path '$longest_path' ", "was probably deleted:\n", @@ -4419,6 +4633,7 @@ package Git::SVN::Log; use strict; use warnings; use POSIX qw/strftime/; +use Time::Local; use constant commit_log_separator => ('-' x 72) . "\n"; use vars qw/$TZ $limit $color $pager $non_recursive $verbose $oneline %rusers $show_commit $incremental/; @@ -4525,7 +4740,12 @@ sub run_pager { } sub format_svn_date { - return strftime("%Y-%m-%d %H:%M:%S %z (%a, %d %b %Y)", localtime(shift)); + # some systmes don't handle or mishandle %z, so be creative. + my $t = shift || time; + my $gm = timelocal(gmtime($t)); + my $sign = qw( + + - )[ $t <=> $gm ]; + my $gmoff = sprintf("%s%02d%02d", $sign, (gmtime(abs($t - $gm)))[2,1]); + return strftime("%Y-%m-%d %H:%M:%S $gmoff (%a, %d %b %Y)", localtime($t)); } sub parse_git_date { diff --git a/git-web--browse.sh b/git-web--browse.sh index 78d236b77f..7ed0faddcd 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -115,7 +115,7 @@ if test -z "$browser" ; then browser_candidates="open $browser_candidates" fi # /bin/start indicates MinGW - if test -n /bin/start; then + if test -x /bin/start; then browser_candidates="start $browser_candidates" fi @@ -159,7 +159,7 @@ static int handle_alias(int *argcp, const char ***argv) if (ret >= 0 && WIFEXITED(ret) && WEXITSTATUS(ret) != 127) exit(WEXITSTATUS(ret)); - die("Failed to run '%s' when expanding alias '%s'\n", + die("Failed to run '%s' when expanding alias '%s'", alias_string + 1, alias_command); } count = split_cmdline(alias_string, &new_argv); @@ -425,27 +425,38 @@ static void execv_dashed_external(const char **argv) strbuf_release(&cmd); } - -int main(int argc, const char **argv) +static int run_argv(int *argcp, const char ***argv) { - const char *cmd = argv[0] && *argv[0] ? argv[0] : "git-help"; - char *slash = (char *)cmd + strlen(cmd); int done_alias = 0; - /* - * 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. - */ - do - --slash; - while (cmd <= slash && !is_dir_sep(*slash)); - if (cmd <= slash) { - *slash++ = 0; - git_set_argv0_path(cmd); - cmd = slash; + while (1) { + /* See if it's an internal command */ + handle_internal_command(*argcp, *argv); + + /* .. then try the external ones */ + execv_dashed_external(*argv); + + /* It could be an alias -- this works around the insanity + * of overriding "git log" with "git show" by having + * alias.log = show + */ + if (done_alias || !handle_alias(argcp, argv)) + break; + done_alias = 1; } + return done_alias; +} + + +int main(int argc, const char **argv) +{ + const char *cmd; + + cmd = git_extract_argv0_path(argv[0]); + if (!cmd) + cmd = "git-help"; + /* * "git-xxxx" is the same as "git xxxx", but we obviously: * @@ -489,31 +500,22 @@ int main(int argc, const char **argv) setup_path(); while (1) { - /* See if it's an internal command */ - handle_internal_command(argc, argv); - - /* .. then try the external ones */ - execv_dashed_external(argv); - - /* It could be an alias -- this works around the insanity - * of overriding "git log" with "git show" by having - * alias.log = show - */ - if (done_alias || !handle_alias(&argc, &argv)) + static int done_help = 0; + static int was_alias = 0; + was_alias = run_argv(&argc, &argv); + if (errno != ENOENT) break; - done_alias = 1; - } - - if (errno == ENOENT) { - if (done_alias) { + if (was_alias) { fprintf(stderr, "Expansion of alias '%s' failed; " "'%s' is not a git-command\n", cmd, argv[0]); exit(1); } - argv[0] = help_unknown_cmd(cmd); - handle_internal_command(argc, argv); - execv_dashed_external(argv); + if (!done_help) { + cmd = argv[0] = help_unknown_cmd(cmd); + done_help = 1; + } else + break; } fprintf(stderr, "Failed to run command '%s': %s\n", diff --git a/gitk-git/gitk b/gitk-git/gitk index dc2a439618..1773ae63eb 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -701,16 +701,17 @@ proc newvarc {view id} { } proc splitvarc {p v} { - global varcid varcstart varccommits varctok + global varcid varcstart varccommits varctok vtokmod global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins set oa $varcid($v,$p) + set otok [lindex $varctok($v) $oa] set ac $varccommits($v,$oa) set i [lsearch -exact $varccommits($v,$oa) $p] if {$i <= 0} return set na [llength $varctok($v)] # "%" sorts before "0"... - set tok "[lindex $varctok($v) $oa]%[strrep $i]" + set tok "$otok%[strrep $i]" lappend varctok($v) $tok lappend varcrow($v) {} lappend varcix($v) {} @@ -730,6 +731,9 @@ proc splitvarc {p v} { for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} { lset vupptr($v) $b $na } + if {[string compare $otok $vtokmod($v)] <= 0} { + modify_arc $v $oa + } } proc renumbervarc {a v} { @@ -3363,7 +3367,6 @@ proc external_blame {parent_idx {line {}}} { # being given an absolute path... set f [make_relative $f] lappend cmdline $base_commit $f - puts "cmdline={$cmdline}" if {[catch {eval exec $cmdline &} err]} { error_popup "[mc "git gui blame: command failed:"] $err" } @@ -5731,7 +5734,6 @@ proc drawcommits {row {endrow {}}} { optimize_rows $ro1 0 $r2 if {$need_redisplay || $nrows_drawn > 2000} { clear_display - drawvisible } # make the lines join to already-drawn rows either side diff --git a/gitweb/README b/gitweb/README index 19ae28ef9b..8433dd1d45 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 @@ -329,6 +327,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 bdaa4e9463..33ef190ceb 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -27,13 +27,29 @@ our $version = "++GIT_VERSION++"; our $my_url = $cgi->url(); our $my_uri = $cgi->url(-absolute => 1); -# if we're called with PATH_INFO, we have to strip that -# from the URL to find our real URL -# we make $path_info global because it's also used later on +# Base URL for relative URLs in gitweb ($logo, $favicon, ...), +# needed and used only for URLs with nonempty PATH_INFO +our $base_url = $my_url; + +# When the script is used as DirectoryIndex, the URL does not contain the name +# of the script file itself, and $cgi->url() fails to strip PATH_INFO, so we +# have to do it ourselves. We make $path_info global because it's also used +# later on. +# +# Another issue with the script being the DirectoryIndex is that the resulting +# $my_url data is not the full script URL: this is good, because we want +# generated links to keep implying the script name if it wasn't explicitly +# indicated in the URL we're handling, but it means that $my_url cannot be used +# as base URL. +# Therefore, if we needed to strip PATH_INFO, then we know that we have +# to build the base URL ourselves: our $path_info = $ENV{"PATH_INFO"}; if ($path_info) { - $my_url =~ s,\Q$path_info\E$,,; - $my_uri =~ s,\Q$path_info\E$,,; + if ($my_url =~ s,\Q$path_info\E$,, && + $my_uri =~ s,\Q$path_info\E$,, && + defined $ENV{'SCRIPT_NAME'}) { + $base_url = $cgi->url(-base => 1) . $ENV{'SCRIPT_NAME'}; + } } # core git executable to use @@ -207,7 +223,7 @@ our %feature = ( # $feature{'blame'}{'override'} = 1; # and in project config gitweb.blame = 0|1; 'blame' => { - 'sub' => \&feature_blame, + 'sub' => sub { feature_bool('blame', @_) }, 'override' => 0, 'default' => [0]}, @@ -245,7 +261,7 @@ our %feature = ( # $feature{'grep'}{'override'} = 1; # and in project config gitweb.grep = 0|1; 'grep' => { - 'sub' => \&feature_grep, + 'sub' => sub { feature_bool('grep', @_) }, 'override' => 0, 'default' => [1]}, @@ -259,7 +275,7 @@ our %feature = ( # $feature{'pickaxe'}{'override'} = 1; # and in project config gitweb.pickaxe = 0|1; 'pickaxe' => { - 'sub' => \&feature_pickaxe, + 'sub' => sub { feature_bool('pickaxe', @_) }, 'override' => 0, 'default' => [1]}, @@ -334,6 +350,21 @@ our %feature = ( 'ctags' => { 'override' => 0, 'default' => [0]}, + + # The maximum number of patches in a patchset generated in patch + # view. Set this to 0 or undef to disable patch view, or to a + # negative number to remove any limit. + + # To disable system wide have in $GITWEB_CONFIG + # $feature{'patches'}{'default'} = [0]; + # To have project specific config enable override in $GITWEB_CONFIG + # $feature{'patches'}{'override'} = 1; + # and in project config gitweb.patches = 0|n; + # where n is the maximum number of patches allowed in a patchset. + 'patches' => { + 'sub' => \&feature_patches, + 'override' => 0, + 'default' => [16]}, ); sub gitweb_get_feature { @@ -367,16 +398,17 @@ sub gitweb_check_feature { } -sub feature_blame { - my ($val) = git_get_project_config('blame', '--bool'); +sub feature_bool { + my $key = shift; + my ($val) = git_get_project_config($key, '--bool'); - if ($val eq 'true') { - return 1; + if (!defined $val) { + return ($_[0]); + } elsif ($val eq 'true') { + return (1); } elsif ($val eq 'false') { - return 0; + return (0); } - - return $_[0]; } sub feature_snapshot { @@ -391,25 +423,11 @@ sub feature_snapshot { return @fmts; } -sub feature_grep { - my ($val) = git_get_project_config('grep', '--bool'); - - if ($val eq 'true') { - return (1); - } elsif ($val eq 'false') { - return (0); - } - - return ($_[0]); -} - -sub feature_pickaxe { - my ($val) = git_get_project_config('pickaxe', '--bool'); +sub feature_patches { + my @val = (git_get_project_config('patches', '--int')); - if ($val eq 'true') { - return (1); - } elsif ($val eq 'false') { - return (0); + if (@val) { + return @val; } return ($_[0]); @@ -508,6 +526,8 @@ our %actions = ( "heads" => \&git_heads, "history" => \&git_history, "log" => \&git_log, + "patch" => \&git_patch, + "patches" => \&git_patches, "rss" => \&git_rss, "atom" => \&git_atom, "search" => \&git_search, @@ -834,7 +854,7 @@ sub href (%) { } my $use_pathinfo = gitweb_check_feature('pathinfo'); - if ($use_pathinfo) { + if ($use_pathinfo and defined $params{'project'}) { # try to put as many parameters as possible in PATH_INFO: # - project name # - action @@ -849,7 +869,7 @@ sub href (%) { $href =~ s,/$,,; # Then add the project name, if present - $href .= "/".esc_url($params{'project'}) if defined $params{'project'}; + $href .= "/".esc_url($params{'project'}); delete $params{'project'}; # since we destructively absorb parameters, we keep this @@ -1364,13 +1384,11 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{8,40})/) { - my $hash_text = $1; - my $link = - $cgi->a({-href => href(action=>"object", hash=>$hash_text), - -class => "text"}, $hash_text); - $line =~ s/$hash_text/$link/; - } + $line =~ s{\b([0-9a-fA-F]{8,40})\b}{ + $cgi->a({-href => href(action=>"object", hash=>$1), + -class => "text"}, $1); + }eg; + return $line; } @@ -1894,18 +1912,19 @@ sub git_parse_project_config { return %config; } -# convert config value to boolean, 'true' or 'false' +# convert config value to boolean: 'true' or 'false' # no value, number > 0, 'true' and 'yes' values are true # rest of values are treated as false (never as error) sub config_to_bool { my $val = shift; + return 1 if !defined $val; # section.key + # strip leading and trailing whitespace $val =~ s/^\s+//; $val =~ s/\s+$//; - return (!defined $val || # section.key - ($val =~ /^\d+$/ && $val) || # section.key = 1 + return (($val =~ /^\d+$/ && $val) || # section.key = 1 ($val =~ /^(?:true|yes)$/i)); # section.key = true } @@ -1958,6 +1977,9 @@ sub git_get_project_config { $config_file = "$git_dir/config"; } + # check if config variable (key) exists + return unless exists $config{"gitweb.$key"}; + # ensure given type if (!defined $type) { return $config{"gitweb.$key"}; @@ -2901,9 +2923,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($base_url)."\" />\n"; + } + # print out each stylesheet that exist, providing backwards capability + # for those people who defined $stylesheet in a config file if (defined $stylesheet) { -#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) { @@ -4582,28 +4609,33 @@ sub git_tag { } sub git_blame { - my $fd; - my $ftype; - + # permissions gitweb_check_feature('blame') - or die_error(403, "Blame view not allowed"); + or die_error(403, "Blame view not allowed"); + # error checking die_error(400, "No file name given") unless $file_name; $hash_base ||= git_get_head_hash($project); - die_error(404, "Couldn't find base commit") unless ($hash_base); + die_error(404, "Couldn't find base commit") unless $hash_base; my %co = parse_commit($hash_base) or die_error(404, "Commit not found"); + my $ftype = "blob"; if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") or die_error(404, "Error looking up file"); + } else { + $ftype = git_get_type($hash); + if ($ftype !~ "blob") { + die_error(400, "Object is not a blob"); + } } - $ftype = git_get_type($hash); - if ($ftype !~ "blob") { - die_error(400, "Object is not a blob"); - } - open ($fd, "-|", git_cmd(), "blame", '-p', '--', - $file_name, $hash_base) + + # run git-blame --porcelain + open my $fd, "-|", git_cmd(), "blame", '-p', + $hash_base, '--', $file_name or die_error(500, "Open git-blame failed"); + + # page header git_header_html(); my $formats_nav = $cgi->a({-href => href(action=>"blob", -replay=>1)}, @@ -4617,42 +4649,46 @@ sub git_blame { git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype, $hash_base); - my @rev_color = (qw(light2 dark2)); + + # page body + my @rev_color = qw(light2 dark2); my $num_colors = scalar(@rev_color); my $current_color = 0; - my $last_rev; + my %metainfo = (); + print <<HTML; <div class="page_body"> <table class="blame"> <tr><th>Commit</th><th>Line</th><th>Data</th></tr> HTML - my %metainfo = (); - while (1) { - $_ = <$fd>; - last unless defined $_; + LINE: + while (my $line = <$fd>) { + chomp $line; + # the header: <SHA-1> <src lineno> <dst lineno> [<lines in group>] + # no <lines in group> for subsequent lines in group of lines my ($full_rev, $orig_lineno, $lineno, $group_size) = - /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/; + ($line =~ /^([0-9a-f]{40}) (\d+) (\d+)(?: (\d+))?$/); if (!exists $metainfo{$full_rev}) { $metainfo{$full_rev} = {}; } my $meta = $metainfo{$full_rev}; - while (<$fd>) { - last if (s/^\t//); - if (/^(\S+) (.*)$/) { + my $data; + while ($data = <$fd>) { + chomp $data; + last if ($data =~ s/^\t//); # contents of line + if ($data =~ /^(\S+) (.*)$/) { $meta->{$1} = $2; } } - my $data = $_; - chomp $data; - my $rev = substr($full_rev, 0, 8); + my $short_rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; - my %date = parse_date($meta->{'author-time'}, - $meta->{'author-tz'}); + my %date = + parse_date($meta->{'author-time'}, $meta->{'author-tz'}); my $date = $date{'iso-tz'}; if ($group_size) { - $current_color = ++$current_color % $num_colors; + $current_color = ($current_color + 1) % $num_colors; } - print "<tr class=\"$rev_color[$current_color]\">\n"; + print "<tr id=\"l$lineno\" class=\"$rev_color[$current_color]\">\n"; if ($group_size) { print "<td class=\"sha1\""; print " title=\"". esc_html($author) . ", $date\""; @@ -4661,20 +4697,25 @@ HTML print $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, - esc_html($rev)); + esc_html($short_rev)); print "</td>\n"; } - open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") - or die_error(500, "Open git-rev-parse failed"); - my $parent_commit = <$dd>; - close $dd; - chomp($parent_commit); + my $parent_commit; + if (!exists $meta->{'parent'}) { + open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^") + or die_error(500, "Open git-rev-parse failed"); + $parent_commit = <$dd>; + close $dd; + chomp($parent_commit); + $meta->{'parent'} = $parent_commit; + } else { + $parent_commit = $meta->{'parent'}; + } my $blamed = href(action => 'blame', file_name => $meta->{'filename'}, hash_base => $parent_commit); print "<td class=\"linenr\">"; print $cgi->a({ -href => "$blamed#l$orig_lineno", - -id => "l$lineno", -class => "linenr" }, esc_html($lineno)); print "</td>"; @@ -4685,6 +4726,8 @@ HTML print "</div>"; close $fd or print "Reading blob failed\n"; + + # page footer git_footer_html(); } @@ -5015,6 +5058,15 @@ sub git_log { my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#commitlist >= 100); + my ($patch_max) = gitweb_get_feature('patches'); + if ($patch_max) { + if ($patch_max < 0 || @commitlist <= $patch_max) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"patches", -replay=>1)}, + "patches"); + } + } + git_header_html(); git_print_page_nav('log','', $hash,undef,undef, $paging_nav); @@ -5094,6 +5146,11 @@ sub git_commit { } @$parents ) . ')'; } + if (gitweb_check_feature('patches')) { + $formats_nav .= " | " . + $cgi->a({-href => href(action=>"patch", -replay=>1)}, + "patch"); + } if (!defined $parent) { $parent = "--root"; @@ -5370,7 +5427,14 @@ sub git_blobdiff_plain { } sub git_commitdiff { - my $format = shift || 'html'; + my %params = @_; + my $format = $params{-format} || 'html'; + + my ($patch_max) = gitweb_get_feature('patches'); + if ($format eq 'patch') { + die_error(403, "Patch view not allowed") unless $patch_max; + } + $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash) or die_error(404, "Unknown commit object"); @@ -5385,6 +5449,11 @@ sub git_commitdiff { $formats_nav = $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)}, "raw"); + if ($patch_max) { + $formats_nav .= " | " . + $cgi->a({-href => href(action=>"patch", -replay=>1)}, + "patch"); + } if (defined $hash_parent && $hash_parent ne '-c' && $hash_parent ne '--cc') { @@ -5468,7 +5537,31 @@ sub git_commitdiff { open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts, '-p', $hash_parent_param, $hash, "--" or die_error(500, "Open git-diff-tree failed"); - + } elsif ($format eq 'patch') { + # For commit ranges, we limit the output to the number of + # patches specified in the 'patches' feature. + # For single commits, we limit the output to a single patch, + # diverging from the git-format-patch default. + my @commit_spec = (); + if ($hash_parent) { + if ($patch_max > 0) { + push @commit_spec, "-$patch_max"; + } + push @commit_spec, '-n', "$hash_parent..$hash"; + } else { + if ($params{-single}) { + push @commit_spec, '-1'; + } else { + if ($patch_max > 0) { + push @commit_spec, "-$patch_max"; + } + push @commit_spec, "-n"; + } + push @commit_spec, '--root', $hash; + } + open $fd, "-|", git_cmd(), "format-patch", '--encoding=utf8', + '--stdout', @commit_spec + or die_error(500, "Open git-format-patch failed"); } else { die_error(400, "Unknown commitdiff format"); } @@ -5517,6 +5610,14 @@ sub git_commitdiff { print to_utf8($line) . "\n"; } print "---\n\n"; + } elsif ($format eq 'patch') { + my $filename = basename($project) . "-$hash.patch"; + + print $cgi->header( + -type => 'text/plain', + -charset => 'utf-8', + -expires => $expires, + -content_disposition => 'inline; filename="' . "$filename" . '"'); } # write patch @@ -5538,11 +5639,25 @@ sub git_commitdiff { print <$fd>; close $fd or print "Reading git-diff-tree failed\n"; + } elsif ($format eq 'patch') { + local $/ = undef; + print <$fd>; + close $fd + or print "Reading git-format-patch failed\n"; } } sub git_commitdiff_plain { - git_commitdiff('plain'); + git_commitdiff(-format => 'plain'); +} + +# format-patch-style patches +sub git_patch { + git_commitdiff(-format => 'patch', -single=> 1); +} + +sub git_patches { + git_commitdiff(-format => 'patch'); } sub git_history { @@ -5895,6 +6010,14 @@ sub git_shortlog { $cgi->a({-href => href(-replay=>1, page=>$page+1), -accesskey => "n", -title => "Alt-n"}, "next"); } + my $patch_max = gitweb_check_feature('patches'); + if ($patch_max) { + if ($patch_max < 0 || @commitlist <= $patch_max) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>"patches", -replay=>1)}, + "patches"); + } + } git_header_html(); git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); @@ -5932,7 +6055,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', @@ -5991,7 +6132,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"> @@ -6018,6 +6176,7 @@ XML } else { print "<updated>$latest_date{'iso-8601'}</updated>\n"; } + print "<generator version='$version/$git_version'>gitweb</generator>\n"; } # contents @@ -6139,7 +6298,11 @@ sub git_atom { sub git_opml { my @list = git_get_projects_list(); - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print $cgi->header( + -type => 'text/xml', + -charset => 'utf-8', + -content_disposition => 'inline; filename="opml.xml"'); + print <<XML; <?xml version="1.0" encoding="utf-8"?> <opml version="1.0"> @@ -6163,8 +6326,8 @@ XML } my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; + my $rss = href('project' => $proj{'path'}, 'action' => 'rss', -full => 1); + my $html = href('project' => $proj{'path'}, 'action' => 'summary', -full => 1); print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; } print <<XML; @@ -28,9 +28,25 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat, p->next = NULL; } +static int is_fixed(const char *s) +{ + while (*s && !is_regex_special(*s)) + s++; + return !*s; +} + static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { - int err = regcomp(&p->regexp, p->pattern, opt->regflags); + int err; + + if (opt->fixed || is_fixed(p->pattern)) + p->fixed = 1; + if (opt->regflags & REG_ICASE) + p->fixed = 0; + if (p->fixed) + return; + + err = regcomp(&p->regexp, p->pattern, opt->regflags); if (err) { char errbuf[1024]; char where[1024]; @@ -161,8 +177,7 @@ void compile_grep_patterns(struct grep_opt *opt) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - if (!opt->fixed) - compile_regexp(p, opt); + compile_regexp(p, opt); break; default: opt->extended = 1; @@ -296,7 +311,6 @@ static struct { static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) { int hit = 0; - int at_true_bol = 1; int saved_ch = 0; regmatch_t pmatch[10]; @@ -317,7 +331,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol } again: - if (!opt->fixed) { + if (!p->fixed) { regex_t *exp = &p->regexp; hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), pmatch, 0); @@ -339,7 +353,7 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol * either end of the line, or at word boundary * (i.e. the next char must not be a word char). */ - if ( ((pmatch[0].rm_so == 0 && at_true_bol) || + if ( ((pmatch[0].rm_so == 0) || !word_char(bol[pmatch[0].rm_so-1])) && ((pmatch[0].rm_eo == (eol-bol)) || !word_char(bol[pmatch[0].rm_eo])) ) @@ -351,10 +365,14 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol /* There could be more than one match on the * line, and the first match might not be * strict word match. But later ones could be! + * Forward to the next possible start, i.e. the + * next position following a non-word char. */ bol = pmatch[0].rm_so + bol + 1; - at_true_bol = 0; - goto again; + while (word_char(bol[-1]) && bol < eol) + bol++; + if (bol < eol) + goto again; } } if (p->token == GREP_PATTERN_HEAD && saved_ch) @@ -399,7 +417,7 @@ static int match_expr_eval(struct grep_opt *o, h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1); break; default: - die("Unexpected node type (internal error) %d\n", x->node); + die("Unexpected node type (internal error) %d", x->node); } if (collect_hits) x->hit |= h; @@ -30,6 +30,7 @@ struct grep_pat { const char *pattern; enum grep_header_field field; regex_t regexp; + unsigned fixed:1; }; enum grep_expr_node { diff --git a/hash-object.c b/hash-object.c index adfd5336a3..ebb3bedb07 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]); + argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0); if (write_object) { diff --git a/http-push.c b/http-push.c index f743c9796a..f101df4086 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> @@ -152,6 +153,7 @@ struct remote_lock char *url; char *owner; char *token; + char tmpfile_suffix[41]; time_t start_time; long timeout; int refreshing; @@ -177,6 +179,47 @@ 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 append_remote_object_url(struct strbuf *buf, const char *url, + const char *hex, + int only_two_digit_prefix) +{ + strbuf_addf(buf, "%sobjects/%.*s/", url, 2, hex); + if (!only_two_digit_prefix) + strbuf_addf(buf, "%s", hex+2); +} + static void finish_request(struct transfer_request *request); static void release_request(struct transfer_request *request); @@ -189,6 +232,15 @@ static void process_response(void *callback_data) } #ifdef USE_CURL_MULTI + +static char *get_remote_object_url(const char *url, const char *hex, + int only_two_digit_prefix) +{ + struct strbuf buf = STRBUF_INIT; + append_remote_object_url(&buf, url, hex, only_two_digit_prefix); + return strbuf_detach(&buf, NULL); +} + static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, void *data) { @@ -223,7 +275,6 @@ static void start_fetch_loose(struct transfer_request *request) char *filename; char prevfile[PATH_MAX]; char *url; - char *posn; int prevlocal; unsigned char prev_buf[PREV_BUF_SIZE]; ssize_t prev_read = 0; @@ -273,17 +324,8 @@ static void start_fetch_loose(struct transfer_request *request) git_SHA1_Init(&request->c); - url = xmalloc(strlen(remote->url) + 50); - request->url = xmalloc(strlen(remote->url) + 50); - strcpy(url, remote->url); - posn = url + strlen(remote->url); - strcpy(posn, "objects/"); - posn += 8; - memcpy(posn, hex, 2); - posn += 2; - *(posn++) = '/'; - strcpy(posn, hex + 2); - strcpy(request->url, url); + url = get_remote_object_url(remote->url, hex, 0); + request->url = xstrdup(url); /* If a previous temp file is present, process what was already fetched. */ @@ -356,16 +398,8 @@ static void start_mkcol(struct transfer_request *request) { char *hex = sha1_to_hex(request->obj->sha1); struct active_request_slot *slot; - char *posn; - request->url = xmalloc(strlen(remote->url) + 13); - strcpy(request->url, remote->url); - posn = request->url + strlen(remote->url); - strcpy(posn, "objects/"); - posn += 8; - memcpy(posn, hex, 2); - posn += 2; - strcpy(posn, "/"); + request->url = get_remote_object_url(remote->url, hex, 1); slot = get_active_slot(); slot->callback_func = process_response; @@ -480,7 +514,7 @@ static void start_put(struct transfer_request *request) { char *hex = sha1_to_hex(request->obj->sha1); struct active_request_slot *slot; - char *posn; + struct strbuf buf = STRBUF_INIT; enum object_type type; char hdr[50]; void *unpacked; @@ -519,21 +553,13 @@ static void start_put(struct transfer_request *request) request->buffer.buf.len = stream.total_out; - request->url = xmalloc(strlen(remote->url) + - strlen(request->lock->token) + 51); - strcpy(request->url, remote->url); - posn = request->url + strlen(remote->url); - strcpy(posn, "objects/"); - posn += 8; - memcpy(posn, hex, 2); - posn += 2; - *(posn++) = '/'; - strcpy(posn, hex + 2); - request->dest = xmalloc(strlen(request->url) + 14); - sprintf(request->dest, "Destination: %s", request->url); - posn += 38; - *(posn++) = '_'; - strcpy(posn, request->lock->token); + strbuf_addstr(&buf, "Destination: "); + append_remote_object_url(&buf, remote->url, hex, 0); + request->dest = strbuf_detach(&buf, NULL); + + append_remote_object_url(&buf, remote->url, hex, 0); + strbuf_add(&buf, request->lock->tmpfile_suffix, 41); + request->url = strbuf_detach(&buf, NULL); slot = get_active_slot(); slot->callback_func = process_response; @@ -588,18 +614,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 +642,6 @@ static int refresh_lock(struct remote_lock *lock) lock->refreshing = 0; curl_slist_free_all(dav_headers); - free(if_header); return rc; } @@ -729,7 +748,6 @@ static void finish_request(struct transfer_request *request) aborted = 1; } } else if (request->state == RUN_FETCH_LOOSE) { - fchmod(request->local_fileno, 0444); close(request->local_fileno); request->local_fileno = -1; if (request->curl_result != CURLE_OK && @@ -1111,6 +1129,8 @@ static void handle_lockprop_ctx(struct xml_ctx *ctx, int tag_closed) static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) { struct remote_lock *lock = (struct remote_lock *)ctx->userData; + git_SHA_CTX sha_ctx; + unsigned char lock_token_sha1[20]; if (tag_closed && ctx->cdata) { if (!strcmp(ctx->name, DAV_ACTIVELOCK_OWNER)) { @@ -1123,6 +1143,13 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) } else if (!strcmp(ctx->name, DAV_ACTIVELOCK_TOKEN)) { lock->token = xmalloc(strlen(ctx->cdata) + 1); strcpy(lock->token, ctx->cdata); + + git_SHA1_Init(&sha_ctx); + git_SHA1_Update(&sha_ctx, lock->token, strlen(lock->token)); + git_SHA1_Final(lock_token_sha1, &sha_ctx); + + lock->tmpfile_suffix[0] = '_'; + memcpy(lock->tmpfile_suffix + 1, sha1_to_hex(lock_token_sha1), 40); } } } @@ -1303,14 +1330,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 +1354,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; @@ -1365,7 +1387,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); } @@ -1732,13 +1754,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)); @@ -1757,7 +1776,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", @@ -1767,7 +1785,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; } @@ -1949,15 +1966,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; @@ -1979,7 +1993,6 @@ static void update_remote_info_refs(struct remote_lock *lock) results.curl_result, results.http_code); } } - free(if_header); } strbuf_release(&buffer.buf); } @@ -2185,6 +2198,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); @@ -2267,10 +2282,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/http-walker.c b/http-walker.c index 0dbad3c888..c5a3ea3b31 100644 --- a/http-walker.c +++ b/http-walker.c @@ -231,7 +231,6 @@ static void finish_object_request(struct object_request *obj_req) { struct stat st; - fchmod(obj_req->local, 0444); close(obj_req->local); obj_req->local = -1; if (obj_req->http_code == 416) { diff --git a/imap-send.c b/imap-send.c index 3703dbd1af..f91293c23f 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 @@ -115,9 +116,9 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap) len = vsnprintf(tmp, sizeof(tmp), fmt, ap); if (len < 0) - die("Fatal: Out of memory\n"); + die("Fatal: Out of memory"); if (len >= sizeof(tmp)) - die("imap command overflow !\n"); + die("imap command overflow!"); *strp = xmemdupz(tmp, len); return len; } @@ -482,7 +483,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...) va_start(va, fmt); if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen) - die("Fatal: buffer too small. Please report a bug.\n"); + die("Fatal: buffer too small. Please report a bug."); va_end(va); return ret; } @@ -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 7697b1dfe3..5dfe03ee6c 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>] }"; @@ -177,7 +178,7 @@ static char *open_pack_file(char *pack_name) } else output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); if (output_fd < 0) - die("unable to create %s: %s\n", pack_name, strerror(errno)); + die("unable to create %s: %s", pack_name, strerror(errno)); pack_fd = output_fd; } else { input_fd = open(pack_name, O_RDONLY); @@ -822,8 +823,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } if (move_temp_to_file(curr_pack_name, final_pack_name)) die("cannot store pack file"); - } - if (from_stdin) + } else if (from_stdin) chmod(final_pack_name, 0444); if (final_index_name != curr_index_name) { @@ -834,8 +834,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } if (move_temp_to_file(curr_index_name, final_index_name)) die("cannot store index file"); - } - chmod(final_index_name, 0444); + } else + chmod(final_index_name, 0444); if (!from_stdin) { printf("%s\n", sha1_to_hex(sha1)); @@ -881,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/list-objects.c b/list-objects.c index dd243c7c66..30ded3d4dd 100644 --- a/list-objects.c +++ b/list-objects.c @@ -10,7 +10,7 @@ static void process_blob(struct rev_info *revs, struct blob *blob, - struct object_array *p, + show_object_fn show, struct name_path *path, const char *name) { @@ -23,7 +23,7 @@ static void process_blob(struct rev_info *revs, if (obj->flags & (UNINTERESTING | SEEN)) return; obj->flags |= SEEN; - add_object(obj, p, path, name); + show(obj, path, name); } /* @@ -50,7 +50,7 @@ static void process_blob(struct rev_info *revs, */ static void process_gitlink(struct rev_info *revs, const unsigned char *sha1, - struct object_array *p, + show_object_fn show, struct name_path *path, const char *name) { @@ -59,7 +59,7 @@ static void process_gitlink(struct rev_info *revs, static void process_tree(struct rev_info *revs, struct tree *tree, - struct object_array *p, + show_object_fn show, struct name_path *path, const char *name) { @@ -77,7 +77,7 @@ static void process_tree(struct rev_info *revs, if (parse_tree(tree) < 0) die("bad tree object %s", sha1_to_hex(obj->sha1)); obj->flags |= SEEN; - add_object(obj, p, path, name); + show(obj, path, name); me.up = path; me.elem = name; me.elem_len = strlen(name); @@ -88,14 +88,14 @@ static void process_tree(struct rev_info *revs, if (S_ISDIR(entry.mode)) process_tree(revs, lookup_tree(entry.sha1), - p, &me, entry.path); + show, &me, entry.path); else if (S_ISGITLINK(entry.mode)) process_gitlink(revs, entry.sha1, - p, &me, entry.path); + show, &me, entry.path); else process_blob(revs, lookup_blob(entry.sha1), - p, &me, entry.path); + show, &me, entry.path); } free(tree->buffer); tree->buffer = NULL; @@ -134,16 +134,20 @@ void mark_edges_uninteresting(struct commit_list *list, } } +static void add_pending_tree(struct rev_info *revs, struct tree *tree) +{ + add_pending_object(revs, &tree->object, ""); +} + void traverse_commit_list(struct rev_info *revs, - void (*show_commit)(struct commit *), - void (*show_object)(struct object_array_entry *)) + show_commit_fn show_commit, + show_object_fn show_object) { int i; struct commit *commit; - struct object_array objects = { 0, 0, NULL }; while ((commit = get_revision(revs)) != NULL) { - process_tree(revs, commit->tree, &objects, NULL, ""); + add_pending_tree(revs, commit->tree); show_commit(commit); } for (i = 0; i < revs->pending.nr; i++) { @@ -154,25 +158,22 @@ void traverse_commit_list(struct rev_info *revs, continue; if (obj->type == OBJ_TAG) { obj->flags |= SEEN; - add_object_array(obj, name, &objects); + show_object(obj, NULL, name); continue; } if (obj->type == OBJ_TREE) { - process_tree(revs, (struct tree *)obj, &objects, + process_tree(revs, (struct tree *)obj, show_object, NULL, name); continue; } if (obj->type == OBJ_BLOB) { - process_blob(revs, (struct blob *)obj, &objects, + process_blob(revs, (struct blob *)obj, show_object, NULL, name); continue; } die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name); } - for (i = 0; i < objects.nr; i++) - show_object(&objects.objects[i]); - free(objects.objects); if (revs->pending.nr) { free(revs->pending.objects); revs->pending.nr = 0; diff --git a/list-objects.h b/list-objects.h index 0f41391ecc..0b2de64301 100644 --- a/list-objects.h +++ b/list-objects.h @@ -2,7 +2,7 @@ #define LIST_OBJECTS_H typedef void (*show_commit_fn)(struct commit *); -typedef void (*show_object_fn)(struct object_array_entry *); +typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *); typedef void (*show_edge_fn)(struct commit *); void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn); diff --git a/lockfile.c b/lockfile.c index 8e556ff8c9..3dbb2d1ff9 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(); @@ -161,7 +158,7 @@ static int lock_file(struct lock_file *lk, const char *path, int flags) NORETURN void unable_to_lock_index_die(const char *path, int err) { - if (errno == EEXIST) { + if (err == EEXIST) { die("Unable to create '%s.lock': %s.\n\n" "If no other git process is currently running, this probably means a\n" "git process crashed in this repository earlier. Make sure no other git\n" @@ -187,7 +184,7 @@ int hold_lock_file_for_append(struct lock_file *lk, const char *path, int flags) fd = lock_file(lk, path, flags); if (fd < 0) { if (flags & LOCK_DIE_ON_ERROR) - die("unable to create '%s.lock': %s", path, strerror(errno)); + unable_to_lock_index_die(path, errno); return fd; } diff --git a/log-tree.c b/log-tree.c index 194ddb13da..84a74e544b 100644 --- a/log-tree.c +++ b/log-tree.c @@ -48,7 +48,7 @@ static void show_parents(struct commit *commit, int abbrev) struct commit_list *p; for (p = commit->parents; p ; p = p->next) { struct commit *parent = p->item; - printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev)); + printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev)); } } @@ -280,7 +280,7 @@ void show_log(struct rev_info *opt) putchar('>'); } } - fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); + fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); show_decorations(opt, commit); @@ -348,13 +348,13 @@ void show_log(struct rev_info *opt) putchar('>'); } } - fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), + fputs(find_unique_abbrev(commit->object.sha1, abbrev_commit), stdout); if (opt->print_parents) show_parents(commit, abbrev_commit); if (parent) printf(" (from %s)", - diff_unique_abbrev(parent->object.sha1, + find_unique_abbrev(parent->object.sha1, abbrev_commit)); show_decorations(opt, commit); printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET)); @@ -2,17 +2,140 @@ #include "string-list.h" #include "mailmap.h" -int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev) +#define DEBUG_MAILMAP 0 +#if DEBUG_MAILMAP +#define debug_mm(...) fprintf(stderr, __VA_ARGS__) +#else +static inline void debug_mm(const char *format, ...) {} +#endif + +const char *git_mailmap_file; + +struct mailmap_info { + char *name; + char *email; +}; + +struct mailmap_entry { + /* name and email for the simple mail-only case */ + char *name; + char *email; + + /* name and email for the complex mail and name matching case */ + struct string_list namemap; +}; + +static void free_mailmap_info(void *p, const char *s) +{ + struct mailmap_info *mi = (struct mailmap_info *)p; + debug_mm("mailmap: -- complex: '%s' -> '%s' <%s>\n", s, mi->name, mi->email); + free(mi->name); + free(mi->email); +} + +static void free_mailmap_entry(void *p, const char *s) +{ + struct mailmap_entry *me = (struct mailmap_entry *)p; + debug_mm("mailmap: removing entries for <%s>, with %d sub-entries\n", s, me->namemap.nr); + debug_mm("mailmap: - simple: '%s' <%s>\n", me->name, me->email); + free(me->name); + free(me->email); + + me->namemap.strdup_strings = 1; + string_list_clear_func(&me->namemap, free_mailmap_info); +} + +static void add_mapping(struct string_list *map, + char *new_name, char *new_email, char *old_name, char *old_email) +{ + struct mailmap_entry *me; + int index; + char *p; + + if (old_email) + for (p = old_email; *p; p++) + *p = tolower(*p); + if (new_email) + for (p = new_email; *p; p++) + *p = tolower(*p); + + if (old_email == NULL) { + old_email = new_email; + new_email = NULL; + } + + if ((index = string_list_find_insert_index(map, old_email, 1)) < 0) { + /* mailmap entry exists, invert index value */ + index = -1 - index; + } else { + /* create mailmap entry */ + struct string_list_item *item = string_list_insert_at_index(index, old_email, map); + item->util = xmalloc(sizeof(struct mailmap_entry)); + memset(item->util, 0, sizeof(struct mailmap_entry)); + ((struct mailmap_entry *)item->util)->namemap.strdup_strings = 1; + } + me = (struct mailmap_entry *)map->items[index].util; + + if (old_name == NULL) { + debug_mm("mailmap: adding (simple) entry for %s at index %d\n", old_email, index); + /* Replace current name and new email for simple entry */ + free(me->name); + free(me->email); + if (new_name) + me->name = xstrdup(new_name); + if (new_email) + me->email = xstrdup(new_email); + } else { + struct mailmap_info *mi = xmalloc(sizeof(struct mailmap_info)); + debug_mm("mailmap: adding (complex) entry for %s at index %d\n", old_email, index); + if (new_name) + mi->name = xstrdup(new_name); + if (new_email) + mi->email = xstrdup(new_email); + string_list_insert(old_name, &me->namemap)->util = mi; + } + + debug_mm("mailmap: '%s' <%s> -> '%s' <%s>\n", + old_name, old_email, new_name, new_email); +} + +static char *parse_name_and_email(char *buffer, char **name, char **email) +{ + char *left, *right, *nstart, *nend; + *name = *email = 0; + + if ((left = strchr(buffer, '<')) == NULL) + return NULL; + if ((right = strchr(left+1, '>')) == NULL) + return NULL; + if (left+1 == right) + return NULL; + + /* remove whitespace from beginning and end of name */ + nstart = buffer; + while (isspace(*nstart) && nstart < left) + ++nstart; + nend = left-1; + while (isspace(*nend) && nend > nstart) + --nend; + + *name = (nstart < nend ? nstart : NULL); + *email = left+1; + *(nend+1) = '\0'; + *right++ = '\0'; + + return (*right == '\0' ? NULL : right); +} + +static int read_single_mailmap(struct string_list *map, const char *filename, char **repo_abbrev) { char buffer[1024]; - FILE *f = fopen(filename, "r"); + FILE *f = (filename == NULL ? NULL : fopen(filename, "r")); if (f == NULL) return 1; while (fgets(buffer, sizeof(buffer), f) != NULL) { - char *end_of_name, *left_bracket, *right_bracket; - char *name, *email; - int i; + char *name1 = 0, *email1 = 0, *name2 = 0, *email2 = 0; if (buffer[0] == '#') { static const char abbrev[] = "# repo-abbrev:"; int abblen = sizeof(abbrev) - 1; @@ -36,41 +159,49 @@ int read_mailmap(struct string_list *map, const char *filename, char **repo_abbr } continue; } - if ((left_bracket = strchr(buffer, '<')) == NULL) - continue; - if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL) - continue; - if (right_bracket == left_bracket + 1) - continue; - for (end_of_name = left_bracket; - end_of_name != buffer && isspace(end_of_name[-1]); - end_of_name--) - ; /* keep on looking */ - if (end_of_name == buffer) - continue; - name = xmalloc(end_of_name - buffer + 1); - strlcpy(name, buffer, end_of_name - buffer + 1); - email = xmalloc(right_bracket - left_bracket); - for (i = 0; i < right_bracket - left_bracket - 1; i++) - email[i] = tolower(left_bracket[i + 1]); - email[right_bracket - left_bracket - 1] = '\0'; - string_list_insert(email, map)->util = name; + if ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL) + parse_name_and_email(name2, &name2, &email2); + + if (email1) + add_mapping(map, name1, email1, name2, email2); } fclose(f); return 0; } -int map_email(struct string_list *map, const char *email, char *name, int maxlen) +int read_mailmap(struct string_list *map, char **repo_abbrev) +{ + map->strdup_strings = 1; + /* each failure returns 1, so >1 means both calls failed */ + return read_single_mailmap(map, ".mailmap", repo_abbrev) + + read_single_mailmap(map, git_mailmap_file, repo_abbrev) > 1; +} + +void clear_mailmap(struct string_list *map) +{ + debug_mm("mailmap: clearing %d entries...\n", map->nr); + map->strdup_strings = 1; + string_list_clear_func(map, free_mailmap_entry); + debug_mm("mailmap: cleared\n"); +} + +int map_user(struct string_list *map, + char *email, int maxlen_email, char *name, int maxlen_name) { char *p; struct string_list_item *item; + struct mailmap_entry *me; char buf[1024], *mailbuf; int i; - /* autocomplete common developers */ + /* figure out space requirement for email */ p = strchr(email, '>'); - if (!p) - return 0; + if (!p) { + /* email passed in might not be wrapped in <>, but end with a \0 */ + p = memchr(email, '\0', maxlen_email); + if (p == 0) + return 0; + } if (p - email + 1 < sizeof(buf)) mailbuf = buf; else @@ -80,13 +211,39 @@ int map_email(struct string_list *map, const char *email, char *name, int maxlen for (i = 0; i < p - email; i++) mailbuf[i] = tolower(email[i]); mailbuf[i] = 0; + + debug_mm("map_user: map '%s' <%s>\n", name, mailbuf); item = string_list_lookup(mailbuf, map); + if (item != NULL) { + me = (struct mailmap_entry *)item->util; + if (me->namemap.nr) { + /* The item has multiple items, so we'll look up on name too */ + /* If the name is not found, we choose the simple entry */ + struct string_list_item *subitem = string_list_lookup(name, &me->namemap); + if (subitem) + item = subitem; + } + } if (mailbuf != buf) free(mailbuf); if (item != NULL) { - const char *realname = (const char *)item->util; - strlcpy(name, realname, maxlen); + struct mailmap_info *mi = (struct mailmap_info *)item->util; + if (mi->name == NULL && (mi->email == NULL || maxlen_email == 0)) { + debug_mm("map_user: -- (no simple mapping)\n"); + return 0; + } + if (maxlen_email && mi->email) + strlcpy(email, mi->email, maxlen_email); + if (maxlen_name && mi->name) + strlcpy(name, mi->name, maxlen_name); + debug_mm("map_user: to '%s' <%s>\n", name, mi->email ? mi->email : ""); return 1; } + debug_mm("map_user: --\n"); return 0; } + +int map_email(struct string_list *map, const char *email, char *name, int maxlen) +{ + return map_user(map, (char *)email, 0, name, maxlen); +} @@ -1,7 +1,11 @@ #ifndef MAILMAP_H #define MAILMAP_H -int read_mailmap(struct string_list *map, const char *filename, char **repo_abbrev); +int read_mailmap(struct string_list *map, char **repo_abbrev); +void clear_mailmap(struct string_list *map); + int map_email(struct string_list *mailmap, const char *email, char *name, int maxlen); +int map_user(struct string_list *mailmap, + char *email, int maxlen_email, char *name, int maxlen_name); #endif diff --git a/merge-index.c b/merge-index.c index 7827e87a92..aa9cf23a39 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-recursive.c b/merge-recursive.c index b97026bd5c..9bf5cc7175 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -237,7 +237,7 @@ static int save_files_dirs(const unsigned char *sha1, string_list_insert(newpath, &o->current_file_set); free(newpath); - return READ_TREE_RECURSIVE; + return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); } static int get_files_dirs(struct merge_options *o, struct tree *tree) @@ -1121,21 +1121,13 @@ static int process_entry(struct merge_options *o, o->branch1, o->branch2); clean_merge = mfi.clean; - if (mfi.clean) - update_file(o, 1, mfi.sha, mfi.mode, path); - else if (S_ISGITLINK(mfi.mode)) - output(o, 1, "CONFLICT (submodule): Merge conflict in %s " - "- needs %s", path, sha1_to_hex(b.sha1)); - else { + if (!mfi.clean) { + if (S_ISGITLINK(mfi.mode)) + reason = "submodule"; output(o, 1, "CONFLICT (%s): Merge conflict in %s", reason, path); - - if (o->call_depth) - update_file(o, 0, mfi.sha, mfi.mode, path); - else - update_file_flags(o, mfi.sha, mfi.mode, path, - 0 /* update_cache */, 1 /* update_working_directory */); } + update_file(o, mfi.clean, mfi.sha, mfi.mode, path); } else if (!o_sha && !a_sha && !b_sha) { /* * this entry was deleted altogether. a_mode == 0 means diff --git a/merge-tree.c b/merge-tree.c index 2d1413efbb..f01e7c81ae 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] == '-') { diff --git a/pack-redundant.c b/pack-redundant.c index 25b81a445c..48a12bc135 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -7,6 +7,7 @@ */ #include "cache.h" +#include "exec_cmd.h" #define BLKSIZE 512 @@ -463,7 +464,7 @@ static void minimize(struct pack_list **min) pll_free(perm_all); } if (perm_ok == NULL) - die("Internal error: No complete sets found!\n"); + die("Internal error: No complete sets found!"); /* find the permutation with the smallest size */ perm = perm_ok; @@ -573,14 +574,14 @@ static struct pack_list * add_pack_file(char *filename) struct packed_git *p = packed_git; if (strlen(filename) < 40) - die("Bad pack filename: %s\n", filename); + die("Bad pack filename: %s", filename); while (p) { if (strstr(p->pack_name, filename)) return add_pack(p); p = p->next; } - die("Filename %s not found in packed_git\n", filename); + die("Filename %s not found in packed_git", filename); } static void load_all(void) @@ -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++) { @@ -636,7 +639,7 @@ int main(int argc, char **argv) add_pack_file(*(argv + i++)); if (local_packs == NULL) - die("Zero packs found!\n"); + die("Zero packs found!"); load_all_objects(); @@ -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 9eb55cc8b5..4c5d09dd25 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 034162ec69..912290549b 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 871f1d20c0..0df4cb086b 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; } @@ -311,36 +311,49 @@ char *enter_repo(char *path, int strict) return NULL; } -int adjust_shared_perm(const char *path) +int set_shared_perm(const char *path, int mode) { struct stat st; - int mode; + int tweak, shared, orig_mode; - if (!shared_repository) + if (!shared_repository) { + if (mode) + return chmod(path, mode & ~S_IFMT); return 0; - if (lstat(path, &st) < 0) - return -1; - mode = st.st_mode; - - if (shared_repository) { - int tweak = shared_repository; - if (!(mode & S_IWUSR)) - tweak &= ~0222; - mode |= tweak; - } else { - /* Preserve old PERM_UMASK behaviour */ - if (mode & S_IWUSR) - mode |= S_IWGRP; } + if (!mode) { + if (lstat(path, &st) < 0) + return -1; + mode = st.st_mode; + orig_mode = mode; + } else + orig_mode = 0; + if (shared_repository < 0) + shared = -shared_repository; + else + shared = shared_repository; + tweak = shared; + + if (!(mode & S_IWUSR)) + tweak &= ~0222; + if (mode & S_IXUSR) + /* Copy read bits to execute bits */ + tweak |= (tweak & 0444) >> 2; + if (shared_repository < 0) + mode = (mode & ~0777) | tweak; + else + mode |= tweak; if (S_ISDIR(mode)) { - mode |= FORCE_DIR_SET_GID; - /* Copy read bits to execute bits */ - mode |= (shared_repository & 0444) >> 2; + mode |= (shared & 0444) >> 2; + mode |= FORCE_DIR_SET_GID; } - if ((mode & st.st_mode) != mode && chmod(path, mode) < 0) + if (((shared_repository < 0 + ? (orig_mode & (FORCE_DIR_SET_GID | 0777)) + : (orig_mode & mode)) != mode) && + chmod(path, (mode & ~S_IFMT)) < 0) return -2; return 0; } @@ -499,3 +512,39 @@ int longest_ancestor_length(const char *path, const char *prefix_list) return max_len; } + +/* strip arbitrary amount of directory separators at end of path */ +static inline int chomp_trailing_dir_sep(const char *path, int len) +{ + while (len && is_dir_sep(path[len - 1])) + len--; + return len; +} + +/* + * If path ends with suffix (complete path components), returns the + * part before suffix (sans trailing directory separators). + * Otherwise returns NULL. + */ +char *strip_path_suffix(const char *path, const char *suffix) +{ + int path_len = strlen(path), suffix_len = strlen(suffix); + + while (suffix_len) { + if (!path_len) + return NULL; + + if (is_dir_sep(path[path_len - 1])) { + if (!is_dir_sep(suffix[suffix_len - 1])) + return NULL; + path_len = chomp_trailing_dir_sep(path, path_len); + suffix_len = chomp_trailing_dir_sep(suffix, suffix_len); + } + else if (path[--path_len] != suffix[--suffix_len]) + return NULL; + } + + if (path_len && !is_dir_sep(path[path_len - 1])) + return NULL; + return xstrndup(path, chomp_trailing_dir_sep(path, path_len)); +} @@ -6,6 +6,7 @@ #include "string-list.h" #include "mailmap.h" #include "log-tree.h" +#include "color.h" static char *user_format; @@ -74,8 +75,7 @@ static int get_one_line(const char *msg) /* High bit set, or ISO-2022-INT */ int non_ascii(int ch) { - ch = (ch & 0xff); - return ((ch & 0x80) || (ch == 0x1b)); + return !isascii(ch) || ch == '\033'; } static int is_rfc2047_special(char ch) @@ -209,15 +209,13 @@ static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb, while (parent) { struct commit *p = parent->item; const char *hex = NULL; - const char *dots; if (abbrev) hex = find_unique_abbrev(p->object.sha1, abbrev); if (!hex) hex = sha1_to_hex(p->object.sha1); - dots = (abbrev && strlen(hex) != 40) ? "..." : ""; parent = parent->next; - strbuf_addf(sb, " %s%s", hex, dots); + strbuf_addf(sb, " %s", hex); } strbuf_addch(sb, '\n'); } @@ -304,23 +302,14 @@ static char *logmsg_reencode(const struct commit *commit, return out; } -static int mailmap_name(struct strbuf *sb, const char *email) +static int mailmap_name(char *email, int email_len, char *name, int name_len) { static struct string_list *mail_map; - char buffer[1024]; - if (!mail_map) { mail_map = xcalloc(1, sizeof(*mail_map)); - read_mailmap(mail_map, ".mailmap", NULL); + read_mailmap(mail_map, NULL); } - - if (!mail_map->nr) - return -1; - - if (!map_email(mail_map, email, buffer, sizeof(buffer))) - return -1; - strbuf_addstr(sb, buffer); - return 0; + return mail_map->nr && map_user(mail_map, email, email_len, name, name_len); } static size_t format_person_part(struct strbuf *sb, char part, @@ -331,6 +320,9 @@ static size_t format_person_part(struct strbuf *sb, char part, int start, end, tz = 0; unsigned long date = 0; char *ep; + const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len; + char person_name[1024]; + char person_mail[1024]; /* advance 'end' to point to email start delimiter */ for (end = 0; end < len && msg[end] != '<'; end++) @@ -344,25 +336,34 @@ static size_t format_person_part(struct strbuf *sb, char part, if (end >= len - 2) goto skip; + /* Seek for both name and email part */ + name_start = msg; + name_end = msg+end; + while (name_end > name_start && isspace(*(name_end-1))) + name_end--; + mail_start = msg+end+1; + mail_end = mail_start; + while (mail_end < msg_end && *mail_end != '>') + mail_end++; + if (mail_end == msg_end) + goto skip; + end = mail_end-msg; + + if (part == 'N' || part == 'E') { /* mailmap lookup */ + strlcpy(person_name, name_start, name_end-name_start+1); + strlcpy(person_mail, mail_start, mail_end-mail_start+1); + mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name)); + name_start = person_name; + name_end = name_start + strlen(person_name); + mail_start = person_mail; + mail_end = mail_start + strlen(person_mail); + } if (part == 'n' || part == 'N') { /* name */ - while (end > 0 && isspace(msg[end - 1])) - end--; - if (part != 'N' || !msg[end] || !msg[end + 1] || - mailmap_name(sb, msg + end + 2) < 0) - strbuf_add(sb, msg, end); + strbuf_add(sb, name_start, name_end-name_start); return placeholder_len; } - start = ++end; /* save email start position */ - - /* advance 'end' to point to email end delimiter */ - for ( ; end < len && msg[end] != '>'; end++) - ; /* do nothing */ - - if (end >= len) - goto skip; - - if (part == 'e') { /* email */ - strbuf_add(sb, msg + start, end - start); + if (part == 'e' || part == 'E') { /* email */ + strbuf_add(sb, mail_start, mail_end-mail_start); return placeholder_len; } @@ -554,6 +555,17 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, /* these are independent of the commit */ switch (placeholder[0]) { case 'C': + if (placeholder[1] == '(') { + const char *end = strchr(placeholder + 2, ')'); + char color[COLOR_MAXLEN]; + if (!end) + return 0; + color_parse_mem(placeholder + 2, + end - (placeholder + 2), + "--pretty format", color); + strbuf_addstr(sb, color); + return end - placeholder + 1; + } if (!prefixcmp(placeholder + 1, "red")) { strbuf_addstr(sb, "\033[31m"); return 4; @@ -275,10 +275,8 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) list = get_ref_dir(ref, list); continue; } - if (!resolve_ref(ref, sha1, 1, &flag)) { - error("%s points nowhere!", ref); - continue; - } + if (!resolve_ref(ref, sha1, 1, &flag)) + hashclr(sha1); list = add_ref(ref, sha1, flag, list, NULL); } free(ref); @@ -287,6 +285,35 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list) return sort_ref_list(list); } +struct warn_if_dangling_data { + const char *refname; + const char *msg_fmt; +}; + +static int warn_if_dangling_symref(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + struct warn_if_dangling_data *d = cb_data; + const char *resolves_to; + unsigned char junk[20]; + + if (!(flags & REF_ISSYMREF)) + return 0; + + resolves_to = resolve_ref(refname, junk, 0, NULL); + if (!resolves_to || strcmp(resolves_to, d->refname)) + return 0; + + printf(d->msg_fmt, refname); + return 0; +} + +void warn_dangling_symref(const char *msg_fmt, const char *refname) +{ + struct warn_if_dangling_data data = { refname, msg_fmt }; + for_each_rawref(warn_if_dangling_symref, &data); +} + static struct ref_list *get_loose_refs(void) { if (!cached_refs.did_loose) { @@ -498,16 +525,19 @@ int read_ref(const char *ref, unsigned char *sha1) return -1; } +#define DO_FOR_EACH_INCLUDE_BROKEN 01 static int do_one_ref(const char *base, each_ref_fn fn, int trim, - void *cb_data, struct ref_list *entry) + int flags, void *cb_data, struct ref_list *entry) { if (strncmp(base, entry->name, trim)) return 0; - if (is_null_sha1(entry->sha1)) - return 0; - if (!has_sha1_file(entry->sha1)) { - error("%s does not point to a valid object!", entry->name); - return 0; + if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { + if (is_null_sha1(entry->sha1)) + return 0; + if (!has_sha1_file(entry->sha1)) { + error("%s does not point to a valid object!", entry->name); + return 0; + } } current_ref = entry; return fn(entry->name + trim, entry->sha1, entry->flag, cb_data); @@ -561,7 +591,7 @@ fallback: } static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, - void *cb_data) + int flags, void *cb_data) { int retval = 0; struct ref_list *packed = get_packed_refs(); @@ -570,7 +600,7 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, struct ref_list *extra; for (extra = extra_refs; extra; extra = extra->next) - retval = do_one_ref(base, fn, trim, cb_data, extra); + retval = do_one_ref(base, fn, trim, flags, cb_data, extra); while (packed && loose) { struct ref_list *entry; @@ -586,13 +616,13 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim, entry = packed; packed = packed->next; } - retval = do_one_ref(base, fn, trim, cb_data, entry); + retval = do_one_ref(base, fn, trim, flags, cb_data, entry); if (retval) goto end_each; } for (packed = packed ? packed : loose; packed; packed = packed->next) { - retval = do_one_ref(base, fn, trim, cb_data, packed); + retval = do_one_ref(base, fn, trim, flags, cb_data, packed); if (retval) goto end_each; } @@ -614,22 +644,28 @@ int head_ref(each_ref_fn fn, void *cb_data) int for_each_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/", fn, 0, cb_data); + return do_for_each_ref("refs/", fn, 0, 0, cb_data); } int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags/", fn, 10, cb_data); + return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data); } int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads/", fn, 11, cb_data); + return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data); } int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes/", fn, 13, cb_data); + return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data); +} + +int for_each_rawref(each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref("refs/", fn, 0, + DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } /* @@ -1453,7 +1489,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 +1500,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 +1543,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)); @@ -24,6 +24,11 @@ extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); +/* can be used to learn about broken ref and symref */ +extern int for_each_rawref(each_ref_fn, void *); + +extern void warn_dangling_symref(const char *msg_fmt, const char *refname); + /* * Extra refs will be listed by for_each_ref() before any actual refs * for the duration of this process or until clear_extra_refs() is @@ -60,6 +65,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, @@ -4,6 +4,7 @@ #include "commit.h" #include "diff.h" #include "revision.h" +#include "dir.h" static struct refspec s_tag_refspec = { 0, @@ -37,6 +38,7 @@ static int branches_nr; static struct branch *current_branch; static const char *default_remote_name; +static int explicit_default_remote_name; static struct rewrite **rewrite; static int rewrite_alloc; @@ -329,8 +331,10 @@ static int handle_config(const char *key, const char *value, void *cb) if (!value) return config_error_nonbool(key); branch->remote_name = xstrdup(value); - if (branch == current_branch) + if (branch == current_branch) { default_remote_name = branch->remote_name; + explicit_default_remote_name = 1; + } } else if (!strcmp(subkey, ".merge")) { if (!value) return config_error_nonbool(key); @@ -634,10 +638,7 @@ static struct refspec *parse_push_refspec(int nr_refspec, const char **refspec) static int valid_remote_nick(const char *name) { - if (!name[0] || /* not empty */ - (name[0] == '.' && /* not "." */ - (!name[1] || /* not ".." */ - (name[1] == '.' && !name[2])))) + if (!name[0] || is_dot_or_dotdot(name)) return 0; return !strchr(name, '/'); /* no slash */ } @@ -645,10 +646,16 @@ static int valid_remote_nick(const char *name) struct remote *remote_get(const char *name) { struct remote *ret; + int name_given = 0; read_config(); - if (!name) + if (name) + name_given = 1; + else { name = default_remote_name; + name_given = explicit_default_remote_name; + } + ret = make_remote(name, 0); if (valid_remote_nick(name)) { if (!ret->url) @@ -656,7 +663,7 @@ struct remote *remote_get(const char *name) if (!ret->url) read_branches_file(ret); } - if (!ret->url) + if (name_given && !ret->url) add_url_alias(ret, name); if (!ret->url) return NULL; @@ -290,7 +290,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) hex = xstrdup(sha1_to_hex(sha1)); string_list_insert(path, rr)->util = hex; if (mkdir(git_path("rr-cache/%s", hex), 0755)) - continue;; + continue; handle_file(path, NULL, rr_path(hex, "preimage")); fprintf(stderr, "Recorded preimage for '%s'\n", path); } diff --git a/revision.c b/revision.c index 34ee490ea0..d7d345bdd9 100644 --- a/revision.c +++ b/revision.c @@ -15,9 +15,9 @@ volatile show_early_output_fn_t show_early_output; -static char *path_name(struct name_path *path, const char *name) +char *path_name(const struct name_path *path, const char *name) { - struct name_path *p; + const struct name_path *p; char *n, *m; int nlen = strlen(name); int len = nlen + 1; diff --git a/revision.h b/revision.h index 66d211ac2e..bfe27071bf 100644 --- a/revision.h +++ b/revision.h @@ -144,6 +144,8 @@ struct name_path { const char *elem; }; +char *path_name(const struct name_path *path, const char *name); + extern void add_object(struct object *obj, struct object_array *p, struct name_path *path, diff --git a/run-command.c b/run-command.c index 44fccc9d5e..b05c734d05 100644 --- a/run-command.c +++ b/run-command.c @@ -352,3 +352,48 @@ int finish_async(struct async *async) #endif return ret; } + +int run_hook(const char *index_file, const char *name, ...) +{ + struct child_process hook; + const char **argv = NULL, *env[2]; + char index[PATH_MAX]; + va_list args; + int ret; + size_t i = 0, alloc = 0; + + if (access(git_path("hooks/%s", name), X_OK) < 0) + return 0; + + va_start(args, name); + ALLOC_GROW(argv, i + 1, alloc); + argv[i++] = git_path("hooks/%s", name); + while (argv[i-1]) { + ALLOC_GROW(argv, i + 1, alloc); + argv[i++] = va_arg(args, const char *); + } + va_end(args); + + memset(&hook, 0, sizeof(hook)); + hook.argv = argv; + hook.no_stdin = 1; + hook.stdout_to_stderr = 1; + if (index_file) { + snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file); + env[0] = index; + env[1] = NULL; + hook.env = env; + } + + ret = start_command(&hook); + free(argv); + if (ret) { + warning("Could not spawn %s", argv[0]); + return ret; + } + ret = finish_command(&hook); + if (ret == -ERR_RUN_COMMAND_WAITPID_SIGNAL) + warning("%s exited due to uncaught signal", argv[0]); + + return ret; +} diff --git a/run-command.h b/run-command.h index e90d9282ff..e345502843 100644 --- a/run-command.h +++ b/run-command.h @@ -10,7 +10,7 @@ enum { ERR_RUN_COMMAND_WAITPID_SIGNAL, ERR_RUN_COMMAND_WAITPID_NOEXIT, }; -#define IS_RUN_COMMAND_ERR(x) ((x) <= -ERR_RUN_COMMAND_FORK) +#define IS_RUN_COMMAND_ERR(x) (-(x) >= ERR_RUN_COMMAND_FORK) struct child_process { const char **argv; @@ -50,6 +50,8 @@ int start_command(struct child_process *); int finish_command(struct child_process *); int run_command(struct child_process *); +extern int run_hook(const char *index_file, const char *name, ...); + #define RUN_COMMAND_NO_STDIN 1 #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ #define RUN_COMMAND_STDOUT_TO_STDERR 4 @@ -434,7 +434,7 @@ int git_config_perm(const char *var, const char *value) /* * Treat values 0, 1 and 2 as compatibility cases, otherwise it is - * a chmod value. + * a chmod value to restrict to. */ switch (i) { case PERM_UMASK: /* 0 */ @@ -456,7 +456,7 @@ int git_config_perm(const char *var, const char *value) * Mask filemode value. Others can not get write permission. * x flags for directories are handled separately. */ - return i & 0666; + return -(i & 0666); } int check_repository_format_version(const char *var, const char *value, void *cb) diff --git a/sha1_file.c b/sha1_file.c index 2320400b7c..8fe135dc61 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -801,7 +801,7 @@ unsigned char* use_pack(struct packed_git *p, if (p->pack_fd == -1 && open_packed_git(p)) die("packfile %s cannot be accessed", p->pack_name); - /* Since packfiles end in a hash of their content and its + /* Since packfiles end in a hash of their content and it's * pointless to ask for an offset into the middle of that * hash, and the in_window function above wouldn't match * don't allow an offset too close to the end of the file. @@ -1708,6 +1708,9 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, delta_base_cache_lru.prev = &ent->lru; } +static void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size); + static void *unpack_delta_entry(struct packed_git *p, struct pack_window **w_curs, off_t curpos, @@ -2111,8 +2114,8 @@ int pretend_sha1_file(void *buf, unsigned long len, enum object_type type, return 0; } -void *read_object(const unsigned char *sha1, enum object_type *type, - unsigned long *size) +static void *read_object(const unsigned char *sha1, enum object_type *type, + unsigned long *size) { unsigned long mapsize; void *map, *buf; @@ -2213,11 +2216,15 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len, } /* - * Move the just written object into its final resting place + * Move the just written object into its final resting place. + * NEEDSWORK: this should be renamed to finalize_temp_file() as + * "moving" is only a part of what it does, when no patch between + * master to pu changes the call sites of this function. */ int move_temp_to_file(const char *tmpfile, const char *filename) { int ret = 0; + if (link(tmpfile, filename)) ret = errno; @@ -2229,12 +2236,12 @@ int move_temp_to_file(const char *tmpfile, const char *filename) * * The same holds for FAT formatted media. * - * When this succeeds, we just return 0. We have nothing + * When this succeeds, we just return. We have nothing * left to unlink. */ if (ret && ret != EEXIST) { if (!rename(tmpfile, filename)) - return 0; + goto out; ret = errno; } unlink(tmpfile); @@ -2245,6 +2252,9 @@ int move_temp_to_file(const char *tmpfile, const char *filename) /* FIXME!!! Collision check here ? */ } +out: + if (set_shared_perm(filename, (S_IFREG|0444))) + return error("unable to set permission to '%s'", filename); return 0; } @@ -2269,7 +2279,6 @@ static void close_sha1_file(int fd) { if (fsync_object_files) fsync_or_die(fd, "sha1 file"); - fchmod(fd, 0444); if (close(fd) != 0) die("error when closing sha1 file (%s)", strerror(errno)); } diff --git a/sha1_name.c b/sha1_name.c index 722fc35a6d..2f75179f4c 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; @@ -248,22 +268,27 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) char fullref[PATH_MAX]; unsigned char sha1_from_ref[20]; unsigned char *this_result; + int flag; this_result = refs_found ? sha1_from_ref : sha1; mksnpath(fullref, sizeof(fullref), *p, len, str); - r = resolve_ref(fullref, this_result, 1, NULL); + r = resolve_ref(fullref, this_result, 1, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); if (!warn_ambiguous_refs) break; - } + } else if ((flag & REF_ISSYMREF) && + (len != 4 || strcmp(str, "HEAD"))) + warning("ignoring dangling symref %s.", fullref); } + 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 +319,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 +335,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 (len && str[len-1] == '}') { - for (at = 0; at < len - 1; at++) { + for (at = len-2; at >= 0; at--) { if (str[at] == '@' && str[at+1] == '{') { reflog_len = (len-1) - (at+2); len = at; @@ -324,6 +352,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 +417,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 +710,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 0000000000..1118b99e57 --- /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 0000000000..618083bce0 --- /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 */ @@ -256,18 +256,21 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder, size_t strbuf_fread(struct strbuf *sb, size_t size, FILE *f) { size_t res; + size_t oldalloc = sb->alloc; strbuf_grow(sb, size); res = fread(sb->buf + sb->len, 1, size, f); - if (res > 0) { + if (res > 0) strbuf_setlen(sb, sb->len + res); - } + else if (res < 0 && oldalloc == 0) + strbuf_release(sb); return res; } ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) { size_t oldlen = sb->len; + size_t oldalloc = sb->alloc; strbuf_grow(sb, hint ? hint : 8192); for (;;) { @@ -275,7 +278,10 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) cnt = xread(fd, sb->buf + sb->len, sb->alloc - sb->len - 1); if (cnt < 0) { - strbuf_setlen(sb, oldlen); + if (oldalloc == 0) + strbuf_release(sb); + else + strbuf_setlen(sb, oldlen); return -1; } if (!cnt) @@ -292,6 +298,8 @@ ssize_t strbuf_read(struct strbuf *sb, int fd, size_t hint) int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) { + size_t oldalloc = sb->alloc; + if (hint < 32) hint = 32; @@ -311,7 +319,8 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) /* .. the buffer was too small - try again */ hint *= 2; } - strbuf_release(sb); + if (oldalloc == 0) + strbuf_release(sb); return -1; } diff --git a/string-list.c b/string-list.c index ddd83c8c76..15e14cf47a 100644 --- a/string-list.c +++ b/string-list.c @@ -26,10 +26,10 @@ static int get_entry_index(const struct string_list *list, const char *string, } /* returns -1-index if already exists */ -static int add_entry(struct string_list *list, const char *string) +static int add_entry(int insert_at, struct string_list *list, const char *string) { - int exact_match; - int index = get_entry_index(list, string, &exact_match); + int exact_match = 0; + int index = insert_at != -1 ? insert_at : get_entry_index(list, string, &exact_match); if (exact_match) return -1 - index; @@ -53,7 +53,13 @@ static int add_entry(struct string_list *list, const char *string) struct string_list_item *string_list_insert(const char *string, struct string_list *list) { - int index = add_entry(list, string); + return string_list_insert_at_index(-1, string, list); +} + +struct string_list_item *string_list_insert_at_index(int insert_at, + const char *string, struct string_list *list) +{ + int index = add_entry(insert_at, list, string); if (index < 0) index = -1 - index; @@ -68,6 +74,16 @@ int string_list_has_string(const struct string_list *list, const char *string) return exact_match; } +int string_list_find_insert_index(const struct string_list *list, const char *string, + int negative_existing_index) +{ + int exact_match; + int index = get_entry_index(list, string, &exact_match); + if (exact_match) + index = -1 - (negative_existing_index ? index : 0); + return index; +} + struct string_list_item *string_list_lookup(const char *string, struct string_list *list) { int exact_match, i = get_entry_index(list, string, &exact_match); @@ -94,6 +110,25 @@ void string_list_clear(struct string_list *list, int free_util) list->nr = list->alloc = 0; } +void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc) +{ + if (list->items) { + int i; + if (clearfunc) { + for (i = 0; i < list->nr; i++) + clearfunc(list->items[i].util, list->items[i].string); + } + if (list->strdup_strings) { + for (i = 0; i < list->nr; i++) + free(list->items[i].string); + } + free(list->items); + } + list->items = NULL; + list->nr = list->alloc = 0; +} + + void print_string_list(const char *text, const struct string_list *p) { int i; diff --git a/string-list.h b/string-list.h index 4d6a7051fe..d32ba05202 100644 --- a/string-list.h +++ b/string-list.h @@ -15,9 +15,18 @@ struct string_list void print_string_list(const char *text, const struct string_list *p); void string_list_clear(struct string_list *list, int free_util); +/* Use this function to call a custom clear function on each util pointer */ +/* The string associated with the util pointer is passed as the second argument */ +typedef void (*string_list_clear_func_t)(void *p, const char *str); +void string_list_clear_func(struct string_list *list, string_list_clear_func_t clearfunc); + /* Use these functions only on sorted lists: */ int string_list_has_string(const struct string_list *list, const char *string); +int string_list_find_insert_index(const struct string_list *list, const char *string, + int negative_existing_index); struct string_list_item *string_list_insert(const char *string, struct string_list *list); +struct string_list_item *string_list_insert_at_index(int insert_at, + const char *string, struct string_list *list); struct string_list_item *string_list_lookup(const char *string, struct string_list *list); /* Use these functions only on unsorted lists: */ diff --git a/symlinks.c b/symlinks.c index 5a5e781a15..f262b7c44b 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-httpd.sh b/t/lib-httpd.sh index 6ac312b905..86cdebc727 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -11,7 +11,21 @@ then exit fi -LIB_HTTPD_PATH=${LIB_HTTPD_PATH-'/usr/sbin/apache2'} +HTTPD_PARA="" + +case $(uname) in + Darwin) + DEFAULT_HTTPD_PATH='/usr/sbin/httpd' + DEFAULT_HTTPD_MODULE_PATH='/usr/libexec/apache2' + HTTPD_PARA="$HTTPD_PARA -DDarwin" + ;; + *) + DEFAULT_HTTPD_PATH='/usr/sbin/apache2' + DEFAULT_HTTPD_MODULE_PATH='/usr/lib/apache2/modules' + ;; +esac + +LIB_HTTPD_PATH=${LIB_HTTPD_PATH-"$DEFAULT_HTTPD_PATH"} LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'8111'} TEST_PATH="$TEST_DIRECTORY"/lib-httpd @@ -20,9 +34,9 @@ HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www if ! test -x "$LIB_HTTPD_PATH" then - say "skipping test, no web server found at '$LIB_HTTPD_PATH'" - test_done - exit + say "skipping test, no web server found at '$LIB_HTTPD_PATH'" + test_done + exit fi HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \ @@ -39,14 +53,12 @@ then exit fi - LIB_HTTPD_MODULE_PATH='/usr/lib/apache2/modules' + LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH" fi else error "Could not identify web server at '$LIB_HTTPD_PATH'" fi -HTTPD_PARA="" - prepare_httpd() { mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH" @@ -84,7 +96,7 @@ prepare_httpd() { start_httpd() { prepare_httpd - trap 'stop_httpd; die' exit + trap 'stop_httpd; die' EXIT "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ -f "$TEST_PATH/apache.conf" $HTTPD_PARA \ @@ -92,8 +104,8 @@ start_httpd() { } stop_httpd() { - trap 'die' exit + trap 'die' EXIT "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ - -f "$TEST_PATH/apache.conf" -k stop + -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop } diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index fdb19a50f1..af6e5e1d6a 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -5,6 +5,12 @@ LogFormat "%h %l %u %t \"%r\" %>s %b" common CustomLog access.log common ErrorLog error.log +<IfDefine Darwin> + LoadModule log_config_module modules/mod_log_config.so + LockFile accept.lock + PidFile httpd.pid +</IfDefine> + <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh new file mode 100644 index 0000000000..260a231933 --- /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 0000000000..09f855af3e --- /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/t0060-path-utils.sh b/t/t0060-path-utils.sh index 4ed1f0b4dd..8336114f98 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -85,4 +85,8 @@ ancestor /foo/bar :://foo/.:: 4 ancestor /foo/bar //foo/./::/bar 4 ancestor /foo/bar ::/bar -1 +test_expect_success 'strip_path_suffix' ' + test c:/msysgit = $(test-path-utils strip_path_suffix \ + c:/msysgit/libexec//git-core libexec/git-core) +' test_done diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh new file mode 100755 index 0000000000..680d7d6861 --- /dev/null +++ b/t/t0070-fundamental.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +test_description='check that the most basic functions work + + +Verify wrappers and compatibility functions. +' + +. ./test-lib.sh + +test_expect_success 'character classes (isspace, isalpha etc.)' ' + test-ctype +' + +test_done diff --git a/t/t0100-previous.sh b/t/t0100-previous.sh new file mode 100755 index 0000000000..315b9b3f10 --- /dev/null +++ b/t/t0100-previous.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +test_description='previous branch syntax @{-n}' + +. ./test-lib.sh + +test_expect_success 'branch -d @{-1}' ' + test_commit A && + git checkout -b junk && + git checkout - && + test "$(git symbolic-ref HEAD)" = refs/heads/master && + git branch -d @{-1} && + test_must_fail git rev-parse --verify refs/heads/junk +' + +test_expect_success 'branch -d @{-12} when there is not enough switches yet' ' + git reflog expire --expire=now && + git checkout -b junk2 && + git checkout - && + test "$(git symbolic-ref HEAD)" = refs/heads/master && + test_must_fail git branch -d @{-12} && + git rev-parse --verify refs/heads/master +' + +test_expect_success 'merge @{-1}' ' + git checkout A && + test_commit B && + git checkout A && + test_commit C && + git branch -f master B && + git branch -f other && + git checkout other && + git checkout master && + git merge @{-1} && + git cat-file commit HEAD | grep "Merge branch '\''other'\''" +' + +test_expect_success 'merge @{-1} when there is not enough switches yet' ' + git reflog expire --expire=now && + git checkout -f master && + git reset --hard B && + git branch -f other C && + git checkout other && + git checkout master && + test_must_fail git merge @{-12} +' + +test_done + diff --git a/t/t1008-read-tree-overlay.sh b/t/t1008-read-tree-overlay.sh new file mode 100755 index 0000000000..f9e00285db --- /dev/null +++ b/t/t1008-read-tree-overlay.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +test_description='test multi-tree read-tree without merging' + +. ./test-lib.sh + +test_expect_success setup ' + echo one >a && + git add a && + git commit -m initial && + git tag initial && + echo two >b && + git add b && + git commit -m second && + git checkout -b side initial && + echo three >a && + mkdir b && + echo four >b/c && + git add b/c && + git commit -m third +' + +test_expect_success 'multi-read' ' + git read-tree initial master side && + (echo a; echo b/c) >expect && + git ls-files >actual && + test_cmp expect actual +' + +test_done + diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 11b82f43dd..3c06842d99 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -336,10 +336,10 @@ test_expect_success 'get bool variable with empty value' \ 'git config --bool emptyvalue.variable > output && cmp output expect' -git config > output 2>&1 - -test_expect_success 'no arguments, but no crash' \ - "test $? = 129 && grep usage output" +test_expect_success 'no arguments, but no crash' ' + test_must_fail git config >output 2>&1 && + grep usage output +' cat > .git/config << EOF [a.b] @@ -373,7 +373,7 @@ EOF test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect' test_expect_success 'alternative GIT_CONFIG (non-existing file should fail)' \ - 'git config --file non-existing-config -l; test $? != 0' + 'test_must_fail git config --file non-existing-config -l' cat > other-config << EOF [ein] diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 653362ba22..3fddc9ee78 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -126,4 +126,45 @@ test_expect_success 'git reflog expire honors core.sharedRepository' ' esac ' +test_expect_success 'forced modes' ' + mkdir -p templates/hooks && + echo update-server-info >templates/hooks/post-update && + chmod +x templates/hooks/post-update && + echo : >random-file && + mkdir new && + ( + cd new && + umask 002 && + git init --shared=0660 --template=../templates && + >frotz && + git add frotz && + git commit -a -m initial && + git repack + ) && + # List repository files meant to be protected; note that + # COMMIT_EDITMSG does not matter---0mode is not about a + # repository with a work tree. + find new/.git -type f -name COMMIT_EDITMSG -prune -o -print | + xargs ls -ld >actual && + + # Everything must be unaccessible to others + test -z "$(sed -e "/^.......---/d" actual)" && + + # All directories must have either 2770 or 770 + test -z "$(sed -n -e "/^drwxrw[sx]---/d" -e "/^d/p" actual)" && + + # post-update hook must be 0770 + test -z "$(sed -n -e "/post-update/{ + /^-rwxrwx---/d + p + }" actual)" && + + # All files inside objects must be accessible by us + test -z "$(sed -n -e "/objects\//{ + /^d/d + /^-r.-r.----/d + p + }" actual)" +' + test_done diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh new file mode 100755 index 0000000000..7fa5f5b22a --- /dev/null +++ b/t/t1401-symbolic-ref.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='basic symbolic-ref tests' +. ./test-lib.sh + +# If the tests munging HEAD fail, they can break detection of +# the git repo, meaning that further tests will operate on +# the surrounding git repo instead of the trash directory. +reset_to_sane() { + echo ref: refs/heads/foo >.git/HEAD +} + +test_expect_success 'symbolic-ref writes HEAD' ' + git symbolic-ref HEAD refs/heads/foo && + echo ref: refs/heads/foo >expect && + test_cmp expect .git/HEAD +' + +test_expect_success 'symbolic-ref reads HEAD' ' + echo refs/heads/foo >expect && + git symbolic-ref HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'symbolic-ref refuses non-ref for HEAD' ' + test_must_fail git symbolic-ref HEAD foo +' +reset_to_sane + +test_expect_success 'symbolic-ref refuses bare sha1' ' + echo content >file && git add file && git commit -m one + test_must_fail git symbolic-ref HEAD `git rev-parse HEAD` +' +reset_to_sane + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh new file mode 100755 index 0000000000..4597af0eb6 --- /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/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 85da4caa7e..48ee07779d 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -26,21 +26,28 @@ test_rev_parse() { "test '$1' = \"\$(git rev-parse --show-prefix)\"" shift [ $# -eq 0 ] && return + + test_expect_success "$name: git-dir" \ + "test '$1' = \"\$(git rev-parse --git-dir)\"" + shift + [ $# -eq 0 ] && return } -# label is-bare is-inside-git is-inside-work prefix +# label is-bare is-inside-git is-inside-work prefix git-dir + +ROOT=$(pwd) -test_rev_parse toplevel false false true '' +test_rev_parse toplevel false false true '' .git cd .git || exit 1 -test_rev_parse .git/ false true false '' +test_rev_parse .git/ false true false '' . cd objects || exit 1 -test_rev_parse .git/objects/ false true false '' +test_rev_parse .git/objects/ false true false '' "$ROOT/.git" cd ../.. || exit 1 mkdir -p sub/dir || exit 1 cd sub/dir || exit 1 -test_rev_parse subdirectory false false true sub/dir/ +test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git" cd ../.. || exit 1 git config core.bare true diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh index 27dc6c55d5..f6a6f839a1 100755 --- a/t/t1501-worktree.sh +++ b/t/t1501-worktree.sh @@ -92,13 +92,6 @@ cd sub/dir || exit 1 test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/ cd ../../../.. || exit 1 -test_expect_success 'detecting gitdir when cwd is in a subdir of gitdir' ' - (expected=$(pwd)/repo.git && - cd repo.git/refs && - unset GIT_DIR && - test "$expected" = "$(git rev-parse --git-dir)") -' - test_expect_success 'repo finds its work tree' ' (cd repo.git && : > work/sub/dir/untracked && diff --git a/t/t1505-rev-parse-last.sh b/t/t1505-rev-parse-last.sh new file mode 100755 index 0000000000..d709ecf8df --- /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 0000000000..87b30a268c --- /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/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh new file mode 100755 index 0000000000..fda3f0af7e --- /dev/null +++ b/t/t2013-checkout-submodule.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +test_description='checkout can handle submodules' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir submodule && + (cd submodule && + git init && + test_commit first) && + git add submodule && + test_tick && + git commit -m superproject && + (cd submodule && + test_commit second) && + git add submodule && + test_tick && + git commit -m updated.superproject +' + +test_expect_success '"reset <submodule>" updates the index' ' + git update-index --refresh && + git diff-files --quiet && + git diff-index --quiet --cached HEAD && + test_must_fail git reset HEAD^ submodule && + test_must_fail git diff-files --quiet && + git reset submodule && + git diff-files --quiet +' + +test_expect_success '"checkout <submodule>" updates the index only' ' + git update-index --refresh && + git diff-files --quiet && + git diff-index --quiet --cached HEAD && + git checkout HEAD^ submodule && + test_must_fail git diff-files --quiet && + git checkout HEAD submodule && + git diff-files --quiet +' + +test_done diff --git a/t/t2014-switch.sh b/t/t2014-switch.sh new file mode 100755 index 0000000000..ccfb147113 --- /dev/null +++ b/t/t2014-switch.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +test_description='Peter MacMillan' +. ./test-lib.sh + +test_expect_success setup ' + echo Hello >file && + git add file && + test_tick && + git commit -m V1 && + echo Hello world >file && + git add file && + git checkout -b other +' + +test_expect_success 'check all changes are staged' ' + git diff --exit-code +' + +test_expect_success 'second commit' ' + git commit -m V2 +' + +test_expect_success 'check' ' + git diff --cached --exit-code +' + +test_done diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh index e42cbfe6c6..293dc353b1 100755 --- a/t/t2300-cd-to-toplevel.sh +++ b/t/t2300-cd-to-toplevel.sh @@ -10,12 +10,12 @@ test_cd_to_toplevel () { cd '"'$1'"' && . git-sh-setup && cd_to_toplevel && - [ "$(unset PWD; /bin/pwd)" = "$TOPLEVEL" ] + [ "$(pwd -P)" = "$TOPLEVEL" ] ) ' } -TOPLEVEL="$(unset PWD; /bin/pwd)/repo" +TOPLEVEL="$(pwd -P)/repo" mkdir -p repo/sub/dir mv .git repo/ SUBDIRECTORY_OK=1 diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index b7a670ef40..be7ae5a004 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -14,7 +14,8 @@ export GIT_AUTHOR_EMAIL test_expect_success \ 'prepare repository with topic branches' \ - 'echo First > A && + 'git config core.logAllRefUpdates true && + echo First > A && git update-index --add A && git commit -m "Add A." && git checkout -b my-topic-branch && @@ -47,6 +48,10 @@ test_expect_success \ 'the rebase operation should not have destroyed author information' \ '! (git log | grep "Author:" | grep "<>")' +test_expect_success 'HEAD was detached during rebase' ' + test $(git rev-parse HEAD@{1}) != $(git rev-parse my-topic-branch@{1}) +' + test_expect_success 'rebase after merge master' ' git reset --hard topic && git merge master && @@ -84,4 +89,10 @@ test_expect_success 'rebase a single mode change' ' GIT_TRACE=1 git rebase master ' +test_expect_success 'Show verbose error when HEAD could not be detached' ' + : > B && + test_must_fail git rebase topic 2> output.err > output.out && + grep "Untracked working tree file .B. would be overwritten" output.err +' + test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 4becc5513d..603b003edf 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" && diff --git a/t/t3409-rebase-hook.sh b/t/t3409-rebase-hook.sh index 1f1b850677..098b75507b 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 5816415aaf..c49143a1a4 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 aacfaae843..6533505218 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 new file mode 100755 index 0000000000..5869061c5b --- /dev/null +++ b/t/t3412-rebase-root.sh @@ -0,0 +1,280 @@ +#!/bin/sh + +test_description='git rebase --root + +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' ' + test_commit 1 A && + test_commit 2 A && + git symbolic-ref HEAD refs/heads/other && + rm .git/index && + test_commit 3 B && + test_commit 1b A 1 && + test_commit 4 B +' + +test_expect_success 'rebase --root expects --onto' ' + test_must_fail git rebase --root +' + +test_expect_success 'setup pre-rebase hook' ' + mkdir -p .git/hooks && + cat >.git/hooks/pre-rebase <<EOF && +#!$SHELL_PATH +echo "\$1,\$2" >.git/PRE-REBASE-INPUT +EOF + chmod +x .git/hooks/pre-rebase +' +cat > expect <<EOF +4 +3 +2 +1 +EOF + +test_expect_success 'rebase --root --onto <newbase>' ' + git checkout -b work && + git rebase --root --onto master && + git log --pretty=tformat:"%s" > rebased && + test_cmp expect rebased +' + +test_expect_success 'pre-rebase got correct input (1)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'rebase --root --onto <newbase> <branch>' ' + git branch work2 other && + git rebase --root --onto master work2 && + git log --pretty=tformat:"%s" > rebased2 && + test_cmp expect rebased2 +' + +test_expect_success 'pre-rebase got correct input (2)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2 +' + +test_expect_success 'rebase -i --root --onto <newbase>' ' + git checkout -b work3 other && + git rebase -i --root --onto master && + git log --pretty=tformat:"%s" > rebased3 && + test_cmp expect rebased3 +' + +test_expect_success 'pre-rebase got correct input (3)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'rebase -i --root --onto <newbase> <branch>' ' + git branch work4 other && + git rebase -i --root --onto master work4 && + git log --pretty=tformat:"%s" > rebased4 && + test_cmp expect rebased4 +' + +test_expect_success 'pre-rebase got correct input (4)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4 +' + +test_expect_success 'rebase -i -p with linear history' ' + git checkout -b work5 other && + git rebase -i -p --root --onto master && + git log --pretty=tformat:"%s" > rebased5 && + test_cmp expect rebased5 +' + +test_expect_success 'pre-rebase got correct input (5)' ' + test "z$(cat .git/PRE-REBASE-INPUT)" = z--root, +' + +test_expect_success 'set up merge history' ' + git checkout other^ && + git checkout -b side && + test_commit 5 C && + git checkout other && + git merge side +' + +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 rebase -i -p --root --onto master && + log_with_names work6 > rebased6 && + test_cmp expect-side rebased6 +' + +test_expect_success 'set up second root and merge' ' + git symbolic-ref HEAD refs/heads/third && + rm .git/index && + rm A B C && + test_commit 6 D && + git checkout other && + git merge third +' + +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 rebase -i -p --root --onto master && + log_with_names work7 > rebased7 && + test_cmp expect-third rebased7 +' + +test_expect_success 'setup pre-rebase hook that fails' ' + mkdir -p .git/hooks && + cat >.git/hooks/pre-rebase <<EOF && +#!$SHELL_PATH +false +EOF + chmod +x .git/hooks/pre-rebase +' + +test_expect_success 'pre-rebase hook stops rebase' ' + git checkout -b stops1 other && + 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 && + 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 02efecae3a..9055c8b318 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/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ index 3ceb8e73c5..bd7f5c0f70 100644 --- a/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ +++ b/t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_ @@ -1,6 +1,6 @@ $ git log --patch-with-stat --summary master -- dir/ commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--patch-with-stat_master b/t/t4013/diff.log_--patch-with-stat_master index 43d77761f9..14595a614c 100644 --- a/t/t4013/diff.log_--patch-with-stat_master +++ b/t/t4013/diff.log_--patch-with-stat_master @@ -1,6 +1,6 @@ $ git log --patch-with-stat master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_ index 5187a26816..5a4e72765d 100644 --- a/t/t4013/diff.log_--patch-with-stat_master_--_dir_ +++ b/t/t4013/diff.log_--patch-with-stat_master_--_dir_ @@ -1,6 +1,6 @@ $ git log --patch-with-stat master -- dir/ commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master index c9640976a8..df0aaa9f2c 100644 --- a/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master +++ b/t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git log --root --cc --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master index ad050af55f..c11b5f2c7f 100644 --- a/t/t4013/diff.log_--root_--patch-with-stat_--summary_master +++ b/t/t4013/diff.log_--root_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git log --root --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_--patch-with-stat_master b/t/t4013/diff.log_--root_--patch-with-stat_master index 628c6c03bc..5f0c98f9ce 100644 --- a/t/t4013/diff.log_--root_--patch-with-stat_master +++ b/t/t4013/diff.log_--root_--patch-with-stat_master @@ -1,6 +1,6 @@ $ git log --root --patch-with-stat master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master index 5d4e0f13b5..e62c368dc6 100644 --- a/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master +++ b/t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git log --root -c --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_-p_master b/t/t4013/diff.log_--root_-p_master index 217a2eb203..b42c334439 100644 --- a/t/t4013/diff.log_--root_-p_master +++ b/t/t4013/diff.log_--root_-p_master @@ -1,6 +1,6 @@ $ git log --root -p master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_--root_master b/t/t4013/diff.log_--root_master index e17ccfc234..e8f46159da 100644 --- a/t/t4013/diff.log_--root_master +++ b/t/t4013/diff.log_--root_master @@ -1,6 +1,6 @@ $ git log --root master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_-p_master b/t/t4013/diff.log_-p_master index f8fefef2c3..bf1326dc36 100644 --- a/t/t4013/diff.log_-p_master +++ b/t/t4013/diff.log_-p_master @@ -1,6 +1,6 @@ $ git log -p master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.log_master b/t/t4013/diff.log_master index e9d9e7b40a..a8f6ce5abd 100644 --- a/t/t4013/diff.log_master +++ b/t/t4013/diff.log_master @@ -1,6 +1,6 @@ $ git log master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.show_master b/t/t4013/diff.show_master index 9e6e1f2710..fb08ce0e46 100644 --- a/t/t4013/diff.show_master +++ b/t/t4013/diff.show_master @@ -1,6 +1,6 @@ $ git show master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master index 5facf2543d..e96ff1fb8c 100644 --- a/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master +++ b/t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git whatchanged --root --cc --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master index 10f6767e49..c0aff68ef6 100644 --- a/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master +++ b/t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master @@ -1,6 +1,6 @@ $ git whatchanged --root -c --patch-with-stat --summary master commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 -Merge: 9a6d494... c7a2ab9... +Merge: 9a6d494 c7a2ab9 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:04:00 2006 +0000 diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh index be541348c6..5b10e976a3 100755 --- a/t/t4018-diff-funcname.sh +++ b/t/t4018-diff-funcname.sh @@ -32,7 +32,7 @@ EOF sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java -builtin_patterns="bibtex html java objc pascal php python ruby tex" +builtin_patterns="bibtex cpp html java objc pascal php python ruby tex" for p in $builtin_patterns do test_expect_success "builtin $p pattern compiles" ' diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 2a72e751e2..0720001281 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -128,6 +128,30 @@ test_expect_success 'force diff with "diff"' ' test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual ' +test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' ' + echo anotherfile > file2 && + git add file2 && + git commit -m "added 2nd file" && + echo modified >file2 && + GIT_EXTERNAL_DIFF=echo git diff +' + +echo "#!$SHELL_PATH" >fake-diff.sh +cat >> fake-diff.sh <<\EOF +cat $2 >> crlfed.txt +EOF +chmod a+x fake-diff.sh + +keep_only_cr () { + tr -dc '\015' +} + +test_expect_success 'external diff with autocrlf = true' ' + git config core.autocrlf true && + GIT_EXTERNAL_DIFF=./fake-diff.sh git diff && + test $(wc -l < crlfed.txt) = $(cat crlfed.txt | keep_only_cr | wc -c) +' + test_expect_success 'diff --cached' ' git add file && git update-index --assume-unchanged file && diff --git a/t/t4032-diff-inter-hunk-context.sh b/t/t4032-diff-inter-hunk-context.sh new file mode 100755 index 0000000000..e4e3e28fc7 --- /dev/null +++ b/t/t4032-diff-inter-hunk-context.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='diff hunk fusing' + +. ./test-lib.sh + +f() { + echo $1 + i=1 + while test $i -le $2 + do + echo $i + i=$(expr $i + 1) + done + echo $3 +} + +t() { + case $# in + 4) hunks=$4; cmd="diff -U$3";; + 5) hunks=$5; cmd="diff -U$3 --inter-hunk-context=$4";; + esac + label="$cmd, $1 common $2" + file=f$1 + expected=expected.$file.$3.$hunks + + if ! test -f $file + then + f A $1 B >$file + git add $file + git commit -q -m. $file + f X $1 Y >$file + fi + + test_expect_success "$label: count hunks ($hunks)" " + test $(git $cmd $file | grep '^@@ ' | wc -l) = $hunks + " + + test -f $expected && + test_expect_success "$label: check output" " + git $cmd $file | grep -v '^index ' >actual && + test_cmp $expected actual + " +} + +cat <<EOF >expected.f1.0.1 || exit 1 +diff --git a/f1 b/f1 +--- a/f1 ++++ b/f1 +@@ -1,3 +1,3 @@ +-A ++X + 1 +-B ++Y +EOF + +cat <<EOF >expected.f1.0.2 || exit 1 +diff --git a/f1 b/f1 +--- a/f1 ++++ b/f1 +@@ -1 +1 @@ +-A ++X +@@ -3 +3 @@ A +-B ++Y +EOF + +# common lines ctx intrctx hunks +t 1 line 0 2 +t 1 line 0 0 2 +t 1 line 0 1 1 +t 1 line 0 2 1 +t 1 line 1 1 + +t 2 lines 0 2 +t 2 lines 0 0 2 +t 2 lines 0 1 2 +t 2 lines 0 2 1 +t 2 lines 1 1 + +t 3 lines 1 2 +t 3 lines 1 0 2 +t 3 lines 1 1 1 +t 3 lines 1 2 1 + +t 9 lines 3 2 +t 9 lines 3 2 2 +t 9 lines 3 3 1 + +test_done diff --git a/t/t4033-diff-patience.sh b/t/t4033-diff-patience.sh new file mode 100755 index 0000000000..1eb14989df --- /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 0000000000..4508effcaa --- /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/t4106-apply-stdin.sh b/t/t4106-apply-stdin.sh new file mode 100755 index 0000000000..72467a1e8e --- /dev/null +++ b/t/t4106-apply-stdin.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='git apply --numstat - <patch' + +. ./test-lib.sh + +test_expect_success setup ' + echo hello >text && + git add text && + echo goodbye >text && + git diff >patch +' + +test_expect_success 'git apply --numstat - < patch' ' + echo "1 1 text" >expect && + git apply --numstat - <patch >actual && + test_cmp expect actual +' + +test_expect_success 'git apply --numstat - < patch patch' ' + for i in 1 2; do echo "1 1 text"; done >expect && + git apply --numstat - < patch patch >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4130-apply-criss-cross-rename.sh b/t/t4130-apply-criss-cross-rename.sh new file mode 100755 index 0000000000..7cfa2d6287 --- /dev/null +++ b/t/t4130-apply-criss-cross-rename.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +test_description='git apply handling criss-cross rename patch.' +. ./test-lib.sh + +create_file() { + cnt=0 + while test $cnt -le 100 + do + cnt=$(($cnt + 1)) + echo "$2" >> "$1" + done +} + +test_expect_success 'setup' ' + create_file file1 "File1 contents" && + create_file file2 "File2 contents" && + create_file file3 "File3 contents" && + git add file1 file2 file3 && + git commit -m 1 +' + +test_expect_success 'criss-cross rename' ' + mv file1 tmp && + mv file2 file1 && + mv tmp file2 && + cp file1 file1-swapped && + cp file2 file2-swapped +' + +test_expect_success 'diff -M -B' ' + git diff -M -B > diff && + git reset --hard + +' + +test_expect_success 'apply' ' + git apply diff && + test_cmp file1 file1-swapped && + test_cmp file2 file2-swapped +' + +test_expect_success 'criss-cross rename' ' + git reset --hard && + mv file1 tmp && + mv file2 file1 && + mv file3 file2 + mv tmp file3 && + cp file1 file1-swapped && + cp file2 file2-swapped && + cp file3 file3-swapped +' + +test_expect_success 'diff -M -B' ' + git diff -M -B > diff && + git reset --hard +' + +test_expect_success 'apply' ' + git apply diff && + test_cmp file1 file1-swapped && + test_cmp file2 file2-swapped && + test_cmp file3 file3-swapped +' + +test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 796f795267..5e65afa0c1 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/t4202-log.sh b/t/t4202-log.sh index 0ab925c4e4..7b976ee36d 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -16,27 +16,31 @@ test_expect_success setup ' test_tick && git commit -m second && - mkdir a && - echo ni >a/two && - git add a/two && + git mv one ichi && test_tick && git commit -m third && - echo san >a/three && - git add a/three && + cp ichi ein && + git add ein && test_tick && git commit -m fourth && - git rm a/three && + mkdir a && + echo ni >a/two && + git add a/two && + test_tick && + git commit -m fifth && + + git rm a/two && test_tick && - git commit -m fifth + git commit -m sixth ' test_expect_success 'diff-filter=A' ' actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) && - expect=$(echo fourth ; echo third ; echo initial) && + expect=$(echo fifth ; echo fourth ; echo third ; echo initial) && test "$actual" = "$expect" || { echo Oops echo "Actual: $actual" @@ -60,7 +64,43 @@ test_expect_success 'diff-filter=M' ' test_expect_success 'diff-filter=D' ' actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) && - expect=$(echo fifth) && + expect=$(echo sixth ; echo third) && + test "$actual" = "$expect" || { + echo Oops + echo "Actual: $actual" + false + } + +' + +test_expect_success 'diff-filter=R' ' + + actual=$(git log -M --pretty="format:%s" --diff-filter=R HEAD) && + expect=$(echo third) && + test "$actual" = "$expect" || { + echo Oops + echo "Actual: $actual" + false + } + +' + +test_expect_success 'diff-filter=C' ' + + actual=$(git log -C -C --pretty="format:%s" --diff-filter=C HEAD) && + expect=$(echo fourth) && + test "$actual" = "$expect" || { + echo Oops + echo "Actual: $actual" + false + } + +' + +test_expect_success 'git log --follow' ' + + actual=$(git log --follow --pretty="format:%s" ichi) && + expect=$(echo third ; echo second ; echo initial) && test "$actual" = "$expect" || { echo Oops echo "Actual: $actual" @@ -72,6 +112,7 @@ test_expect_success 'diff-filter=D' ' test_expect_success 'setup case sensitivity tests' ' echo case >one && test_tick && + git add one git commit -a -m Second ' diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh new file mode 100755 index 0000000000..9a7d1b4466 --- /dev/null +++ b/t/t4203-mailmap.sh @@ -0,0 +1,215 @@ +#!/bin/sh + +test_description='.mailmap configurations' + +. ./test-lib.sh + +test_expect_success setup ' + echo one >one && + git add one && + test_tick && + git commit -m initial && + echo two >>one && + git add one && + git commit --author "nick1 <bugs@company.xx>" -m second +' + +cat >expect <<\EOF +A U Thor (1): + initial + +nick1 (1): + second + +EOF + +test_expect_success 'No mailmap' ' + git shortlog HEAD >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +Repo Guy (1): + initial + +nick1 (1): + second + +EOF + +test_expect_success 'default .mailmap' ' + echo "Repo Guy <author@example.com>" > .mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +# Using a mailmap file in a subdirectory of the repo here, but +# could just as well have been a file outside of the repository +cat >expect <<\EOF +Internal Guy (1): + second + +Repo Guy (1): + initial + +EOF +test_expect_success 'mailmap.file set' ' + mkdir internal_mailmap && + echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap && + git config mailmap.file internal_mailmap/.mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +External Guy (1): + initial + +Internal Guy (1): + second + +EOF +test_expect_success 'mailmap.file override' ' + echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap && + git config mailmap.file internal_mailmap/.mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +Repo Guy (1): + initial + +nick1 (1): + second + +EOF + +test_expect_success 'mailmap.file non-existant' ' + rm internal_mailmap/.mailmap && + rmdir internal_mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +cat >expect <<\EOF +A U Thor (1): + initial + +nick1 (1): + second + +EOF +test_expect_success 'No mailmap files, but configured' ' + rm .mailmap && + git shortlog HEAD >actual && + test_cmp expect actual +' + +# Extended mailmap configurations should give us the following output for shortlog +cat >expect <<\EOF +A U Thor <author@example.com> (1): + initial + +CTO <cto@company.xx> (1): + seventh + +Other Author <other@author.xx> (2): + third + fourth + +Santa Claus <santa.claus@northpole.xx> (2): + fifth + sixth + +Some Dude <some@dude.xx> (1): + second + +EOF + +test_expect_success 'Shortlog output (complex mapping)' ' + echo three >>one && + git add one && + test_tick && + git commit --author "nick2 <bugs@company.xx>" -m third && + + echo four >>one && + git add one && + test_tick && + git commit --author "nick2 <nick2@company.xx>" -m fourth && + + echo five >>one && + git add one && + test_tick && + git commit --author "santa <me@company.xx>" -m fifth && + + echo six >>one && + git add one && + test_tick && + git commit --author "claus <me@company.xx>" -m sixth && + + echo seven >>one && + git add one && + test_tick && + git commit --author "CTO <cto@coompany.xx>" -m seventh && + + mkdir internal_mailmap && + echo "Committed <committer@example.com>" > internal_mailmap/.mailmap && + echo "<cto@company.xx> <cto@coompany.xx>" >> internal_mailmap/.mailmap && + echo "Some Dude <some@dude.xx> nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap && + echo "Other Author <other@author.xx> nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap && + echo "Other Author <other@author.xx> <nick2@company.xx>" >> internal_mailmap/.mailmap && + echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap && + echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap && + + git shortlog -e HEAD >actual && + test_cmp expect actual + +' + +# git log with --pretty format which uses the name and email mailmap placemarkers +cat >expect <<\EOF +Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> + +Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> + +Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> + +Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> + +Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> + +Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> + +Author A U Thor <author@example.com> maps to A U Thor <author@example.com> +Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com> +EOF + +test_expect_success 'Log output (complex mapping)' ' + git log --pretty=format:"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n" >actual && + test_cmp expect actual +' + +# git blame +cat >expect <<\EOF +^3a2fdcb (A U Thor 2005-04-07 15:13:13 -0700 1) one +7de6f99b (Some Dude 2005-04-07 15:13:13 -0700 2) two +5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three +ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four +5ab6d4fa (Santa Claus 2005-04-07 15:16:13 -0700 5) five +38a42d8b (Santa Claus 2005-04-07 15:17:13 -0700 6) six +8ddc0386 (CTO 2005-04-07 15:18:13 -0700 7) seven +EOF + +test_expect_success 'Blame output (complex mapping)' ' + git blame one >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4252-am-options.sh b/t/t4252-am-options.sh index 3ab9e8e6e3..f603c1b133 100755 --- a/t/t4252-am-options.sh +++ b/t/t4252-am-options.sh @@ -1,6 +1,6 @@ #!/bin/sh -test_description='git am not losing options' +test_description='git am with options and not losing them' . ./test-lib.sh tm="$TEST_DIRECTORY/t4252" @@ -50,4 +50,29 @@ test_expect_success 'interrupted am -C1 -p2' ' grep "^Three$" file-2 ' +test_expect_success 'interrupted am --directory="frotz nitfol"' ' + rm -rf .git/rebase-apply && + git reset --hard initial && + test_must_fail git am --directory="frotz nitfol" "$tm"/am-test-5-? && + git am --skip && + grep One "frotz nitfol/file-5" +' + +test_expect_success 'apply to a funny path' ' + with_sq="with'\''sq" + rm -fr .git/rebase-apply && + git reset --hard initial && + git am --directory="$with_sq" "$tm"/am-test-5-2 && + test -f "$with_sq/file-5" +' + +test_expect_success 'am --reject' ' + rm -rf .git/rebase-apply && + git reset --hard initial && + test_must_fail git am --reject "$tm"/am-test-6-1 && + grep "@@ -1,3 +1,3 @@" file-2.rej && + test_must_fail git diff-files --exit-code --quiet file-2 && + grep "[-]-reject" .git/rebase-apply/apply-opt +' + test_done diff --git a/t/t4252/am-test-5-1 b/t/t4252/am-test-5-1 new file mode 100644 index 0000000000..da7bf29cbe --- /dev/null +++ b/t/t4252/am-test-5-1 @@ -0,0 +1,20 @@ +From: A U Thor <au.thor@example.com> +Date: Thu Dec 4 16:00:00 2008 -0800 +Subject: Six + +Applying this patch with --directory='frotz nitfol' should fail + +diff --git i/junk/file-2 w/junk/file-2 +index 06e567b..b6f3a16 100644 +--- i/junk/file-2 ++++ w/junk/file-2 +@@ -1,7 +1,7 @@ + One + 2 +-3 ++Three + 4 + 5 +-6 ++Six + 7 diff --git a/t/t4252/am-test-5-2 b/t/t4252/am-test-5-2 new file mode 100644 index 0000000000..373025bcf6 --- /dev/null +++ b/t/t4252/am-test-5-2 @@ -0,0 +1,15 @@ +From: A U Thor <au.thor@example.com> +Date: Thu Dec 4 16:00:00 2008 -0800 +Subject: Six + +Applying this patch with --directory='frotz nitfol' should succeed + +diff --git i/file-5 w/file-5 +new file mode 100644 +index 000000..1d6ed9f +--- /dev/null ++++ w/file-5 +@@ -0,0 +1,3 @@ ++One ++two ++three diff --git a/t/t4252/am-test-6-1 b/t/t4252/am-test-6-1 new file mode 100644 index 0000000000..a8859e9b8f --- /dev/null +++ b/t/t4252/am-test-6-1 @@ -0,0 +1,21 @@ +From: A U Thor <au.thor@example.com> +Date: Thu Dec 4 16:00:00 2008 -0800 +Subject: Huh + +Should fail and leave rejects + +diff --git i/file-2 w/file-2 +index 06e567b..b6f3a16 100644 +--- i/file-2 ++++ w/file-2 +@@ -1,3 +1,3 @@ +-0 ++One + 2 + 3 +@@ -4,4 +4,4 @@ + 4 + 5 +-6 ++Six + 7 diff --git a/t/t5100/info0001 b/t/t5100/info0001 index 8c052777e0..f951538acc 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/rfc2047-info-0004 b/t/t5100/rfc2047-info-0004 index 0ca7ff0529..f67a90a974 100644 --- a/t/t5100/rfc2047-info-0004 +++ b/t/t5100/rfc2047-info-0004 @@ -1,4 +1,4 @@ -Author: Nathaniel Borenstein (םולש ןב ילטפנ) +Author: Nathaniel Borenstein (םולש ןב ילטפנ) Email: nsb@thumper.bellcore.com Subject: Test of new header generator diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index 85df55f2c4..c5ad206b40 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -2,10 +2,10 @@ From nobody Mon Sep 17 00:00:00 2001 -From: A +From: A (zzz) U Thor - <a.u.thor@example.com> + <a.u.thor@example.com> (Comment) Date: Fri, 9 Jun 2006 00:44:16 -0700 Subject: [PATCH] a commit. diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 884e24253a..e6f70d474f 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -10,6 +10,7 @@ test_expect_success \ 'setup' \ 'rm -rf .git git init && + git config pack.threads 1 && i=1 && while test $i -le 100 do diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 771c0a06a4..55ed7c7935 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -112,4 +112,42 @@ test_expect_success 'prune: do not prune heads listed as an argument' ' ' +test_expect_success 'gc --no-prune' ' + + before=$(git count-objects | sed "s/ .*//") && + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test $((1 + $before)) = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE && + test-chmtime =-$((86400*5001)) $BLOB_FILE && + git config gc.pruneExpire 2.days.ago && + git gc --no-prune && + test 1 = $(git count-objects | sed "s/ .*//") && + test -f $BLOB_FILE + +' + +test_expect_success 'gc respects gc.pruneExpire' ' + + git config gc.pruneExpire 5002.days.ago && + git gc && + test -f $BLOB_FILE && + git config gc.pruneExpire 5000.days.ago && + git gc && + test ! -f $BLOB_FILE + +' + +test_expect_success 'gc --prune=<date>' ' + + BLOB=$(echo aleph_0 | git hash-object -w --stdin) && + BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") && + test-chmtime =-$((86400*5001)) $BLOB_FILE && + git gc --prune=5002.days.ago && + test -f $BLOB_FILE && + git gc --prune=5000.days.ago && + test ! -f $BLOB_FILE + +' + test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index b21317d685..f2d5581b12 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -32,9 +32,7 @@ test_expect_success setup ' done && git update-ref HEAD "$commit" && git clone ./. victim && - cd victim && - git log && - cd .. && + ( cd victim && git log ) && git update-ref HEAD "$zero" && parent=$zero && i=0 && @@ -59,88 +57,84 @@ test_expect_success 'pack the source repository' ' ' test_expect_success 'pack the destination repository' ' + ( cd victim && git repack -a -d && - git prune && - cd .. + git prune + ) ' -test_expect_success \ - 'pushing rewound head should not barf but require --force' ' - # should not fail but refuse to update. - if git send-pack ./victim/.git/ master - then - # now it should fail with Pasky patch - echo >&2 Gaah, it should have failed. - false - else - echo >&2 Thanks, it correctly failed. - true - fi && - if cmp victim/.git/refs/heads/master .git/refs/heads/master - then - # should have been left as it was! - false - else - true - fi && +test_expect_success 'refuse pushing rewound head without --force' ' + pushed_head=$(git rev-parse --verify master) && + victim_orig=$(cd victim && git rev-parse --verify master) && + test_must_fail git send-pack ./victim master && + victim_head=$(cd victim && git rev-parse --verify master) && + test "$victim_head" = "$victim_orig" && # this should update - git send-pack --force ./victim/.git/ master && - cmp victim/.git/refs/heads/master .git/refs/heads/master + git send-pack --force ./victim master && + victim_head=$(cd victim && git rev-parse --verify master) && + test "$victim_head" = "$pushed_head" ' test_expect_success \ 'push can be used to delete a ref' ' - cd victim && - git branch extra master && - cd .. && - test -f victim/.git/refs/heads/extra && - git send-pack ./victim/.git/ :extra master && - ! test -f victim/.git/refs/heads/extra + ( cd victim && git branch extra master ) && + git send-pack ./victim :extra master && + ( cd victim && + test_must_fail git rev-parse --verify extra ) ' -unset GIT_CONFIG -HOME=`pwd`/no-such-directory -export HOME ;# this way we force the victim/.git/config to be used. - -test_expect_success \ - 'pushing a delete should be denied with denyDeletes' ' - cd victim && - git config receive.denyDeletes true && - git branch extra master && - cd .. && - test -f victim/.git/refs/heads/extra && - test_must_fail git send-pack ./victim/.git/ :extra master +test_expect_success 'refuse deleting push with denyDeletes' ' + ( + cd victim && + ( git branch -D extra || : ) && + git config receive.denyDeletes true && + git branch extra master + ) && + test_must_fail git send-pack ./victim :extra master ' -rm -f victim/.git/refs/heads/extra -test_expect_success \ - 'pushing with --force should be denied with denyNonFastforwards' ' - cd victim && - git config receive.denyNonFastforwards true && - cd .. && - git update-ref refs/heads/master master^ || return 1 - git send-pack --force ./victim/.git/ master && return 1 - ! test_cmp .git/refs/heads/master victim/.git/refs/heads/master +test_expect_success 'denyNonFastforwards trumps --force' ' + ( + cd victim && + ( git branch -D extra || : ) && + git config receive.denyNonFastforwards true + ) && + victim_orig=$(cd victim && git rev-parse --verify master) && + test_must_fail git send-pack --force ./victim master^:master && + victim_head=$(cd victim && git rev-parse --verify master) && + test "$victim_orig" = "$victim_head" ' -test_expect_success \ - 'pushing does not include non-head refs' ' - mkdir parent && cd parent && - git init && touch file && git add file && git commit -m add && - cd .. && - git clone parent child && cd child && git push --all && - cd ../parent && - git branch -a >branches && ! grep origin/master branches +test_expect_success 'push --all excludes remote tracking hierarchy' ' + mkdir parent && + ( + cd parent && + git init && : >file && git add file && git commit -m add + ) && + git clone parent child && + ( + cd child && git push --all + ) && + ( + cd parent && + test -z "$(git for-each-ref refs/remotes/origin)" + ) ' rewound_push_setup() { rm -rf parent child && - mkdir parent && cd parent && - git init && echo one >file && git add file && git commit -m one && - echo two >file && git commit -a -m two && - cd .. && - git clone parent child && cd child && git reset --hard HEAD^ + mkdir parent && + ( + cd parent && + git init && + echo one >file && git add file && git commit -m one && + echo two >file && git commit -a -m two + ) && + git clone parent child && + ( + cd child && git reset --hard HEAD^ + ) } rewound_push_succeeded() { @@ -156,30 +150,57 @@ rewound_push_failed() { fi } -test_expect_success \ - 'pushing explicit refspecs respects forcing' ' +test_expect_success 'pushing explicit refspecs respects forcing' ' rewound_push_setup && - if git send-pack ../parent/.git refs/heads/master:refs/heads/master - then - false - else - true - fi && rewound_push_failed && - git send-pack ../parent/.git +refs/heads/master:refs/heads/master && - rewound_push_succeeded + parent_orig=$(cd parent && git rev-parse --verify master) && + ( + cd child && + test_must_fail git send-pack ../parent \ + refs/heads/master:refs/heads/master + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + test "$parent_orig" = "$parent_head" && + ( + cd child && + git send-pack ../parent \ + +refs/heads/master:refs/heads/master + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + child_head=$(cd parent && git rev-parse --verify master) && + test "$parent_head" = "$child_head" ' -test_expect_success \ - 'pushing wildcard refspecs respects forcing' ' +test_expect_success 'pushing wildcard refspecs respects forcing' ' rewound_push_setup && - if git send-pack ../parent/.git refs/heads/*:refs/heads/* - then - false - else - true - fi && rewound_push_failed && - git send-pack ../parent/.git +refs/heads/*:refs/heads/* && - rewound_push_succeeded + parent_orig=$(cd parent && git rev-parse --verify master) && + ( + cd child && + test_must_fail git send-pack ../parent \ + "refs/heads/*:refs/heads/*" + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + test "$parent_orig" = "$parent_head" && + ( + cd child && + git send-pack ../parent \ + "+refs/heads/*:refs/heads/*" + ) && + parent_head=$(cd parent && git rev-parse --verify master) && + child_head=$(cd parent && git rev-parse --verify master) && + test "$parent_head" = "$child_head" +' + +test_expect_success 'warn pushing to delete current branch' ' + rewound_push_setup && + ( + cd child && + git send-pack ../parent :refs/heads/master 2>errs + ) && + grep "warning: to refuse deleting" child/errs && + ( + cd parent && + test_must_fail git rev-parse --verify master + ) ' test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index bc5b7ce4a6..eb637184a0 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -402,4 +402,31 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches' ' test "$(git config remote.origin.fetch)" = "refs/heads/master:refs/heads/origin") ' +test_expect_success 'remote prune to cause a dangling symref' ' + git clone one seven && + ( + cd one && + git checkout side2 && + git branch -D master + ) && + ( + cd seven && + git remote prune origin + ) 2>err && + grep "has become dangling" err && + + : And the dangling symref will not cause other annoying errors + ( + cd seven && + git branch -a + ) 2>err && + ! grep "points nowhere" err + ( + cd seven && + test_must_fail git branch nomore origin + ) 2>err && + grep "dangling symref" err +' + test_done + diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 9e679b402d..bee3424fed 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -191,38 +191,39 @@ test_expect_success 'bundle should be able to create a full history' ' ' -test "$TEST_RSYNC" && { +! rsync --help > /dev/null 2> /dev/null && +say 'Skipping rsync tests because rsync was not found' || { test_expect_success 'fetch via rsync' ' git pack-refs && mkdir rsynced && - cd rsynced && - git init && - git fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master && - git gc --prune && - test $(git rev-parse master) = $(cd .. && git rev-parse master) && - git fsck --full + (cd rsynced && + git init --bare && + git fetch "rsync:$(pwd)/../.git" master:refs/heads/master && + git gc --prune && + test $(git rev-parse master) = $(cd .. && git rev-parse master) && + git fsck --full) ' test_expect_success 'push via rsync' ' - mkdir ../rsynced2 && - (cd ../rsynced2 && + mkdir rsynced2 && + (cd rsynced2 && git init) && - git push rsync://127.0.0.1$(pwd)/../rsynced2/.git master && - cd ../rsynced2 && - git gc --prune && - test $(git rev-parse master) = $(cd .. && git rev-parse master) && - git fsck --full + (cd rsynced && + git push "rsync:$(pwd)/../rsynced2/.git" master) && + (cd rsynced2 && + git gc --prune && + test $(git rev-parse master) = $(cd .. && git rev-parse master) && + git fsck --full) ' test_expect_success 'push via rsync' ' - cd .. && mkdir rsynced3 && (cd rsynced3 && git init) && - git push --all rsync://127.0.0.1$(pwd)/rsynced3/.git && - cd rsynced3 && - test $(git rev-parse master) = $(cd .. && git rev-parse master) && - git fsck --full + git push --all "rsync:$(pwd)/rsynced3/.git" && + (cd rsynced3 && + test $(git rev-parse master) = $(cd .. && git rev-parse master) && + git fsck --full) ' } diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 4426df9226..89649e7a9b 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -492,7 +492,7 @@ test_expect_success 'warn on push to HEAD of non-bare repository' ' git checkout master && git config receive.denyCurrentBranch warn) && git push testrepo master 2>stderr && - grep "warning.*this may cause confusion" stderr + grep "warning: updating the current branch" stderr ' test_expect_success 'deny push to HEAD of non-bare repository' ' @@ -510,7 +510,7 @@ test_expect_success 'allow push to HEAD of bare repository (bare)' ' git config receive.denyCurrentBranch true && git config core.bare true) && git push testrepo master 2>stderr && - ! grep "warning.*this may cause confusion" stderr + ! grep "warning: updating the current branch" stderr ' test_expect_success 'allow push to HEAD of non-bare repository (config)' ' @@ -520,7 +520,7 @@ test_expect_success 'allow push to HEAD of non-bare repository (config)' ' git config receive.denyCurrentBranch false ) && git push testrepo master 2>stderr && - ! grep "warning.*this may cause confusion" stderr + ! grep "warning: updating the current branch" stderr ' test_expect_success 'fetch with branches' ' diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index c236b5e83b..10e5fd0d5a 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -94,6 +94,18 @@ test_expect_success 'MKCOL sends directory names with trailing slashes' ' ' +x1="[0-9a-f]" +x2="$x1$x1" +x5="$x1$x1$x1$x1$x1" +x38="$x5$x5$x5$x5$x5$x5$x5$x1$x1$x1" +x40="$x38$x2" + +test_expect_success 'PUT and MOVE sends object to URLs with SHA-1 hash suffix' ' + sed -e "s/PUT /OP /" -e "s/MOVE /OP /" "$HTTPD_ROOT_PATH"/access.log | + grep -e "\"OP .*/objects/$x2/${x38}_$x40 HTTP/[.0-9]*\" 20[0-9] " + +' + stop_httpd test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 78a3fa639c..44793f2eee 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -125,4 +125,38 @@ test_expect_success 'clone to destination with extra trailing /' ' ' +test_expect_success 'clone to an existing empty directory' ' + mkdir target-3 && + git clone src target-3 && + T=$( cd target-3 && git rev-parse HEAD ) && + S=$( cd src && git rev-parse HEAD ) && + test "$T" = "$S" +' + +test_expect_success 'clone to an existing non-empty directory' ' + mkdir target-4 && + >target-4/Fakefile && + test_must_fail git clone src target-4 +' + +test_expect_success 'clone to an existing path' ' + >target-5 && + test_must_fail git clone src target-5 +' + +test_expect_success 'clone a void' ' + mkdir src-0 && + ( + cd src-0 && git init + ) && + git clone src-0 target-6 && + ( + cd src-0 && test_commit A + ) && + git clone src-0 target-7 && + # There is no reason to insist they are bit-for-bit + # identical, but this test should suffice for now. + test_cmp target-6/.git/config target-7/.git/config +' + test_done diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index fe0fda282c..3559d17964 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -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/t5704-bundle.sh b/t/t5704-bundle.sh new file mode 100755 index 0000000000..a8f4419e61 --- /dev/null +++ b/t/t5704-bundle.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +test_description='some bundle related tests' +. ./test-lib.sh + +test_expect_success 'setup' ' + + : > file && + git add file && + test_tick && + git commit -m initial && + test_tick && + git tag -m tag tag && + : > file2 && + git add file2 && + : > file3 && + test_tick && + git commit -m second && + git add file3 && + test_tick && + git commit -m third + +' + +test_expect_success 'tags can be excluded by rev-list options' ' + + git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 && + git ls-remote bundle > output && + ! grep tag output + +' + +test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 86bf7e14ba..59d1f6283b 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -14,7 +14,7 @@ touch foo && git add foo && git commit -m "added foo" && test_format() { cat >expect.$1 test_expect_success "format $1" " -git rev-list --pretty=format:$2 master >output.$1 && +git rev-list --pretty=format:'$2' master >output.$1 && test_cmp expect.$1 output.$1 " } @@ -101,6 +101,13 @@ commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 [31mfoo[32mbar[34mbaz[mxyzzy EOF +test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<'EOF' +commit 131a310eb913d107dd3c09a65d1651175898735d +[1;31;43mfoo[m +commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873 +[1;31;43mfoo[m +EOF + cat >commit-msg <<'EOF' Test printing of complex bodies diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 6a9936e5c4..329c851685 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -48,6 +48,22 @@ test_expect_success 'result is really identical' ' test $H = $(git rev-parse HEAD) ' +TRASHDIR=$(pwd) +test_expect_success 'correct GIT_DIR while using -d' ' + mkdir drepo && + ( cd drepo && + git init && + test_commit drepo && + git filter-branch -d "$TRASHDIR/dfoo" \ + --index-filter "cp \"$TRASHDIR\"/dfoo/backup-refs \"$TRASHDIR\"" \ + ) && + grep drepo "$TRASHDIR/backup-refs" +' + +test_expect_success 'Fail if commit filter fails' ' + test_must_fail git filter-branch -f --commit-filter "exit 1" HEAD +' + test_expect_success 'rewrite, renaming a specific file' ' git filter-branch -f --tree-filter "mv d doh || :" HEAD ' @@ -264,4 +280,12 @@ test_expect_success 'Tag name filtering allows slashes in tag names' ' test_cmp expect actual ' +test_expect_success 'Prune empty commits' ' + git rev-list HEAD > expect && + make_commit to_remove && + git filter-branch -f --index-filter "git update-index --remove to_remove" --prune-empty HEAD && + git rev-list HEAD > actual && + test_cmp expect actual +' + test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index f377fea4bb..69501e2711 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/t7201-co.sh b/t/t7201-co.sh index 0e21632f19..bdb808af1a 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -171,7 +171,7 @@ test_expect_success 'checkout to detach HEAD' ' git checkout -f renamer && git clean -f && git checkout renamer^ 2>messages && (cat >messages.expect <<EOF -Note: moving to "renamer^" which isn'"'"'t a local branch +Note: moving to '\''renamer^'\'' which isn'\''t a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new_branch_name> diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 2ec7ac6a51..af690ec6c1 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -47,6 +47,55 @@ test_expect_success 'Prepare submodule testing' ' GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git ' +test_expect_success 'Prepare submodule add testing' ' + submodurl=$(pwd) + ( + mkdir addtest && + cd addtest && + git init + ) +' + +test_expect_success 'submodule add' ' + ( + cd addtest && + git submodule add "$submodurl" submod && + git submodule init + ) +' + +test_expect_success 'submodule add with ./ in path' ' + ( + cd addtest && + git submodule add "$submodurl" ././dotsubmod/./frotz/./ && + git submodule init + ) +' + +test_expect_success 'submodule add with // in path' ' + ( + cd addtest && + git submodule add "$submodurl" slashslashsubmod///frotz// && + git submodule init + ) +' + +test_expect_success 'submodule add with /.. in path' ' + ( + cd addtest && + git submodule add "$submodurl" dotdotsubmod/../realsubmod/frotz/.. && + git submodule init + ) +' + +test_expect_success 'submodule add with ./, /.. and // in path' ' + ( + cd addtest && + git submodule add "$submodurl" dot/dotslashsubmod/./../..////realsubmod2/a/b/c/d/../../../../frotz//.. && + git submodule init + ) +' + test_expect_success 'status should fail for unmapped paths' ' if git submodule status then @@ -234,4 +283,17 @@ test_expect_success 'gracefully add submodule with a trailing slash' ' ' +test_expect_success 'ls-files gracefully handles trailing slash' ' + + test "init" = "$(git ls-files init/)" + +' + +test_expect_success 'submodule <invalid-path> warns' ' + + git submodule no-such-submodule 2> output.err && + grep "^error: .*no-such-submodule" output.err + +' + test_done diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh new file mode 100755 index 0000000000..aa6c44ce4f --- /dev/null +++ b/t/t7405-submodule-merge.sh @@ -0,0 +1,74 @@ +#!/bin/sh + +test_description='merging with submodules' + +. ./test-lib.sh + +# +# history +# +# a --- c +# / \ / +# root X +# \ / \ +# b --- d +# + +test_expect_success setup ' + + mkdir sub && + (cd sub && + git init && + echo original > file && + git add file && + test_tick && + git commit -m sub-root) && + git add sub && + test_tick && + git commit -m root && + + git checkout -b a master && + (cd sub && + echo A > file && + git add file && + test_tick && + git commit -m sub-a) && + git add sub && + test_tick && + git commit -m a && + + git checkout -b b master && + (cd sub && + echo B > file && + git add file && + test_tick && + git commit -m sub-b) && + git add sub && + test_tick && + git commit -m b + + git checkout -b c a && + git merge -s ours b && + + git checkout -b d b && + git merge -s ours a +' + +test_expect_failure 'merging with modify/modify conflict' ' + + git checkout -b test1 a && + test_must_fail git merge b && + test -f .git/MERGE_MSG && + git diff + +' + +test_expect_success 'merging with a modify/modify conflict between merge bases' ' + + git reset --hard HEAD && + git checkout -b test2 c && + git merge d + +' + +test_done diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 6e18a96319..5998baf27b 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -149,10 +149,7 @@ EOF test_expect_success '--signoff' ' echo "yet another content *narf*" >> foo && - echo "zort" | ( - test_set_editor "$TEST_DIRECTORY"/t7500/add-content && - git commit -s -F - foo - ) && + echo "zort" | git commit -s -F - foo && git cat-file commit HEAD | sed "1,/^$/d" > output && test_cmp expect output ' diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index 63bfc6d8b3..b4e2b4db84 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -127,6 +127,26 @@ test_expect_success \ "showing committed revisions" \ "git rev-list HEAD >current" +cat >editor <<\EOF +#!/bin/sh +sed -e "s/good/bad/g" < "$1" > "$1-" +mv "$1-" "$1" +EOF +chmod 755 editor + +cat >msg <<EOF +A good commit message. +EOF + +test_expect_success \ + 'editor not invoked if -F is given' ' + echo "moo" >file && + VISUAL=./editor git commit -a -F msg && + git show -s --pretty=format:"%s" | grep -q good && + echo "quack" >file && + echo "Another good message." | VISUAL=./editor git commit -a -F - && + git show -s --pretty=format:"%s" | grep -q good + ' # We could just check the head sha1, but checking each commit makes it # easier to isolate bugs. diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 09fa5f115c..e768c3eb2d 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -9,38 +9,81 @@ Testing basic merge tool invocation' . ./test-lib.sh +# All the mergetool test work by checking out a temporary branch based +# off 'branch1' and then merging in master and checking the results of +# running mergetool + test_expect_success 'setup' ' echo master >file1 && - git add file1 && + mkdir subdir && + echo master sub >subdir/file3 && + git add file1 subdir/file3 && git commit -m "added file1" && + git checkout -b branch1 master && echo branch1 change >file1 && echo branch1 newfile >file2 && - git add file1 file2 && + echo branch1 sub >subdir/file3 && + git add file1 file2 subdir/file3 && git commit -m "branch1 changes" && - git checkout -b branch2 master && - echo branch2 change >file1 && - echo branch2 newfile >file2 && - git add file1 file2 && - git commit -m "branch2 changes" && + git checkout master && echo master updated >file1 && echo master new >file2 && - git add file1 file2 && - git commit -m "master updates" -' + echo master new sub >subdir/file3 && + git add file1 file2 subdir/file3 && + git commit -m "master updates" && -test_expect_success 'custom mergetool' ' git config merge.tool mytool && git config mergetool.mytool.cmd "cat \"\$REMOTE\" >\"\$MERGED\"" && - git config mergetool.mytool.trustExitCode true && - git checkout branch1 && + git config mergetool.mytool.trustExitCode true +' + +test_expect_success 'custom mergetool' ' + git checkout -b test1 branch1 && test_must_fail git merge master >/dev/null 2>&1 && - ( yes "" | git mergetool file1>/dev/null 2>&1 ) && - ( yes "" | git mergetool file2>/dev/null 2>&1 ) && + ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && test "$(cat file1)" = "master updated" && test "$(cat file2)" = "master new" && - git commit -m "branch1 resolved with mergetool" + test "$(cat subdir/file3)" = "master new sub" && + git commit -m "branch1 resolved with mergetool" ' +test_expect_success 'mergetool crlf' ' + git config core.autocrlf true && + git checkout -b test2 branch1 + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file1 >/dev/null 2>&1 ) && + ( yes "" | git mergetool file2 >/dev/null 2>&1 ) && + ( yes "" | git mergetool subdir/file3 >/dev/null 2>&1 ) && + test "$(printf x | cat file1 -)" = "$(printf "master updated\r\nx")" && + test "$(printf x | cat file2 -)" = "$(printf "master new\r\nx")" && + test "$(printf x | cat subdir/file3 -)" = "$(printf "master new sub\r\nx")" && + git commit -m "branch1 resolved with mergetool - autocrlf" && + git config core.autocrlf false && + git reset --hard +' + +test_expect_success 'mergetool in subdir' ' + git checkout -b test3 branch1 + cd subdir && ( + test_must_fail git merge master >/dev/null 2>&1 && + ( yes "" | git mergetool file3 >/dev/null 2>&1 ) && + test "$(cat file3)" = "master new sub" ) +' + +# We can't merge files from parent directories when running mergetool +# from a subdir. Is this a bug? +# +#test_expect_failure 'mergetool in subdir' ' +# cd subdir && ( +# ( yes "" | git mergetool ../file1 >/dev/null 2>&1 ) && +# ( yes "" | git mergetool ../file2 >/dev/null 2>&1 ) && +# test "$(cat ../file1)" = "master updated" && +# test "$(cat ../file2)" = "master new" && +# git commit -m "branch1 resolved with mergetool - subdir" ) +#' + test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 9ce546e3b2..6b29bff782 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -69,6 +69,25 @@ test_expect_success 'packed obs in alt ODB are repacked even when local repo is done ' +test_expect_failure 'packed obs in alt ODB are repacked when local repo has packs' ' + rm -f .git/objects/pack/* && + echo new_content >> file1 && + git add file1 && + git commit -m more_content && + git repack && + git repack -a -d && + myidx=$(ls -1 .git/objects/pack/*.idx) && + test -f "$myidx" && + for p in alt_objects/pack/*.idx; do + git verify-pack -v $p | sed -n -e "/^[0-9a-f]\{40\}/p" + done | while read sha1 rest; do + if ! ( git verify-pack -v $myidx | grep "^$sha1" ); then + echo "Missing object in local pack: $sha1" + return 1 + fi + done +' + test_expect_success 'packed obs in alternate ODB kept pack are repacked' ' # swap the .keep so the commit object is in the pack with .keep for p in alt_objects/pack/*.pack diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 63a8225ae5..5babdf26e6 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/t9001-send-email.sh b/t/t9001-send-email.sh index cb3d183770..d7634187aa 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -32,16 +32,18 @@ clean_fake_sendmail() { } test_expect_success 'Extract patches' ' - patches=`git format-patch -n HEAD^1` + patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1` ' test_expect_success 'Send patches' ' - git send-email --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors + git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors ' cat >expected <<\EOF !nobody@example.com! !author@example.com! +!one@example.com! +!two@example.com! EOF test_expect_success \ 'Verify commandline' \ @@ -50,13 +52,15 @@ test_expect_success \ cat >expected-show-all-headers <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com' +(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com' Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<bcc@example.com> +RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<bcc@example.com> From: Example <from@example.com> To: to@example.com -Cc: cc@example.com, A <author@example.com> +Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -70,6 +74,7 @@ EOF test_expect_success 'Show all headers' ' git send-email \ --dry-run \ + --suppress-cc=sob \ --from="Example <from@example.com>" \ --to=to@example.com \ --cc=cc@example.com \ @@ -104,6 +109,28 @@ test_expect_success 'no patch was sent' ' ! test -e commandline1 ' +test_expect_success 'Author From: in message body' ' + clean_fake_sendmail && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches && + sed "1,/^$/d" < msgtxt1 > msgbody1 + grep "From: A <author@example.com>" msgbody1 +' + +test_expect_success 'Author From: not in message body' ' + clean_fake_sendmail && + git send-email \ + --from="A <author@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches && + sed "1,/^$/d" < msgtxt1 > msgbody1 + ! grep "From: A <author@example.com>" msgbody1 +' + test_expect_success 'allow long lines with --no-validate' ' git send-email \ --from="Example <nobody@example.com>" \ @@ -167,16 +194,18 @@ test_expect_success 'second message is patch' ' grep "Subject:.*Second" msgtxt2 ' -cat >expected-show-all-headers <<\EOF +cat >expected-suppress-sob <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com' +(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com' Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com> +RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com> From: Example <from@example.com> To: to@example.com -Cc: cc@example.com, A <author@example.com> +Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -185,10 +214,10 @@ X-Mailer: X-MAILER-STRING Result: OK EOF -test_expect_success 'sendemail.cc set' ' - git config sendemail.cc cc@example.com && +test_suppression () { git send-email \ --dry-run \ + --suppress-cc=$1 \ --from="Example <from@example.com>" \ --to=to@example.com \ --smtp-server relay.example.com \ @@ -196,20 +225,27 @@ test_expect_success 'sendemail.cc set' ' sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \ -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \ -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \ - >actual-show-all-headers && - test_cmp expected-show-all-headers actual-show-all-headers + >actual-suppress-$1 && + test_cmp expected-suppress-$1 actual-suppress-$1 +} + +test_expect_success 'sendemail.cc set' ' + git config sendemail.cc cc@example.com && + test_suppression sob ' -cat >expected-show-all-headers <<\EOF +cat >expected-suppress-sob <<\EOF 0001-Second.patch (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com' +(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com' Dry-OK. Log says: Server: relay.example.com MAIL FROM:<from@example.com> -RCPT TO:<to@example.com>,<author@example.com> +RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com> From: Example <from@example.com> To: to@example.com -Cc: A <author@example.com> +Cc: A <author@example.com>, One <one@example.com>, two@example.com Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING @@ -220,17 +256,123 @@ EOF test_expect_success 'sendemail.cc unset' ' git config --unset sendemail.cc && - git send-email \ - --dry-run \ - --from="Example <from@example.com>" \ - --to=to@example.com \ - --smtp-server relay.example.com \ - $patches | - sed -e "s/^\(Date:\).*/\1 DATE-STRING/" \ - -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \ - -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \ - >actual-show-all-headers && - test_cmp expected-show-all-headers actual-show-all-headers + test_suppression sob +' + +cat >expected-suppress-all <<\EOF +0001-Second.patch +Dry-OK. Log says: +Server: relay.example.com +MAIL FROM:<from@example.com> +RCPT TO:<to@example.com> +From: Example <from@example.com> +To: to@example.com +Subject: [PATCH 1/1] Second. +Date: DATE-STRING +Message-Id: MESSAGE-ID-STRING +X-Mailer: X-MAILER-STRING + +Result: OK +EOF + +test_expect_success '--suppress-cc=all' ' + test_suppression all +' + +cat >expected-suppress-body <<\EOF +0001-Second.patch +(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com' +(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com' +Dry-OK. Log says: +Server: relay.example.com +MAIL FROM:<from@example.com> +RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com> +From: Example <from@example.com> +To: to@example.com +Cc: A <author@example.com>, One <one@example.com>, two@example.com +Subject: [PATCH 1/1] Second. +Date: DATE-STRING +Message-Id: MESSAGE-ID-STRING +X-Mailer: X-MAILER-STRING + +Result: OK +EOF + +test_expect_success '--suppress-cc=body' ' + test_suppression body +' + +cat >expected-suppress-sob <<\EOF +0001-Second.patch +(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com' +(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com' +Dry-OK. Log says: +Server: relay.example.com +MAIL FROM:<from@example.com> +RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com> +From: Example <from@example.com> +To: to@example.com +Cc: A <author@example.com>, One <one@example.com>, two@example.com +Subject: [PATCH 1/1] Second. +Date: DATE-STRING +Message-Id: MESSAGE-ID-STRING +X-Mailer: X-MAILER-STRING + +Result: OK +EOF + +test_expect_success '--suppress-cc=sob' ' + test_suppression sob +' + +cat >expected-suppress-bodycc <<\EOF +0001-Second.patch +(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com' +(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com' +(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>' +Dry-OK. Log says: +Server: relay.example.com +MAIL FROM:<from@example.com> +RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com> +From: Example <from@example.com> +To: to@example.com +Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com> +Subject: [PATCH 1/1] Second. +Date: DATE-STRING +Message-Id: MESSAGE-ID-STRING +X-Mailer: X-MAILER-STRING + +Result: OK +EOF + +test_expect_success '--suppress-cc=bodycc' ' + test_suppression bodycc +' + +cat >expected-suppress-cc <<\EOF +0001-Second.patch +(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>' +(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>' +Dry-OK. Log says: +Server: relay.example.com +MAIL FROM:<from@example.com> +RCPT TO:<to@example.com>,<author@example.com>,<committer@example.com> +From: Example <from@example.com> +To: to@example.com +Cc: A <author@example.com>, C O Mitter <committer@example.com> +Subject: [PATCH 1/1] Second. +Date: DATE-STRING +Message-Id: MESSAGE-ID-STRING +X-Mailer: X-MAILER-STRING + +Result: OK +EOF + +test_expect_success '--suppress-cc=cc' ' + test_suppression cc ' test_expect_success '--compose adds MIME for utf8 body' ' @@ -313,4 +455,15 @@ test_expect_success 'feed two files' ' test "z$(sed -n -e 2p subjects)" = "zSubject: [PATCH 2/2] add master" ' +test_expect_success 'in-reply-to but no threading' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --in-reply-to="<in-reply-id@example.com>" \ + --no-thread \ + $patches | + grep "In-Reply-To: <in-reply-id@example.com>" +' + test_done diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index 8a9dde44d5..9c7b1ad18b 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -15,8 +15,17 @@ compare_git_head_with () { } compare_svn_head_with () { - LC_ALL=en_US.UTF-8 svn log --limit 1 `git svn info --url` | \ - sed -e 1,3d -e "/^-\{1,\}\$/d" >current && + # extract just the log message and strip out committer info. + # don't use --limit here since svn 1.1.x doesn't have it, + LC_ALL=en_US.UTF-8 svn log `git svn info --url` | perl -w -e ' + use bytes; + $/ = ("-"x72) . "\n"; + my @x = <STDIN>; + @x = split(/\n/, $x[1]); + splice(@x, 0, 2); + $x[-1] = ""; + print join("\n", @x); + ' > current && test_cmp current "$1" } diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh new file mode 100755 index 0000000000..b8fb277562 --- /dev/null +++ b/t/t9130-git-svn-authors-file.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# +# Copyright (c) 2008 Eric Wong +# + +test_description='git svn authors file tests' + +. ./lib-git-svn.sh + +cat > svn-authors <<EOF +aa = AAAAAAA AAAAAAA <aa@example.com> +bb = BBBBBBB BBBBBBB <bb@example.com> +EOF + +test_expect_success 'setup svnrepo' ' + for i in aa bb cc dd + do + svn mkdir -m $i --username $i "$svnrepo"/$i + done + ' + +test_expect_success 'start import with incomplete authors file' ' + ! git svn clone --authors-file=svn-authors "$svnrepo" x + ' + +test_expect_success 'imported 2 revisions successfully' ' + ( + cd x + test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 2 && + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author BBBBBBB BBBBBBB <bb@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ + grep "^author AAAAAAA AAAAAAA <aa@example\.com> " + ) + ' + +cat >> svn-authors <<EOF +cc = CCCCCCC CCCCCCC <cc@example.com> +dd = DDDDDDD DDDDDDD <dd@example.com> +EOF + +test_expect_success 'continues to import once authors have been added' ' + ( + cd x + git svn fetch --authors-file=../svn-authors && + test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 4 && + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author DDDDDDD DDDDDDD <dd@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ + grep "^author CCCCCCC CCCCCCC <cc@example\.com> " + ) + ' + +test_expect_success 'authors-file against globs' ' + svn mkdir -m globs --username aa \ + "$svnrepo"/aa/trunk "$svnrepo"/aa/branches "$svnrepo"/aa/tags && + git svn clone --authors-file=svn-authors -s "$svnrepo"/aa aa-work && + for i in bb ee cc + do + branch="aa/branches/$i" + svn mkdir -m "$branch" --username $i "$svnrepo/$branch" + done + ' + +test_expect_success 'fetch fails on ee' ' + ( cd aa-work && ! git svn fetch --authors-file=../svn-authors ) + ' + +tmp_config_get () { + GIT_CONFIG=.git/svn/.metadata git config --get "$1" +} + +test_expect_success 'failure happened without negative side effects' ' + ( + cd aa-work && + test 6 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" && + test 6 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`" + ) + ' + +cat >> svn-authors <<EOF +ee = EEEEEEE EEEEEEE <ee@example.com> +EOF + +test_expect_success 'fetch continues after authors-file is fixed' ' + ( + cd aa-work && + git svn fetch --authors-file=../svn-authors && + test 8 -eq "`tmp_config_get svn-remote.svn.branches-maxRev`" && + test 8 -eq "`tmp_config_get svn-remote.svn.tags-maxRev`" + ) + ' + +test_done diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh new file mode 100755 index 0000000000..8f35e294aa --- /dev/null +++ b/t/t9131-git-svn-empty-symlink.sh @@ -0,0 +1,110 @@ +#!/bin/sh + +test_description='test that git handles an svn repository with empty symlinks' + +. ./lib-git-svn.sh +test_expect_success 'load svn dumpfile' ' + svnadmin load "$rawsvnrepo" <<EOF +SVN-fs-dump-format-version: 2 + +UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2008-11-26T07:17:27.590577Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 4 +test +K 10 +svn:author +V 12 +normalperson +K 8 +svn:date +V 27 +2008-11-26T07:18:03.511836Z +PROPS-END + +Node-path: bar +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Content-length: 33 + +K 11 +svn:special +V 1 +* +PROPS-END + +Revision-number: 2 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 13 +bar => doink + +K 10 +svn:author +V 12 +normalperson +K 8 +svn:date +V 27 +2008-11-27T03:55:31.601672Z +PROPS-END + +Node-path: bar +Node-kind: file +Node-action: change +Text-content-length: 10 +Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9 +Content-length: 10 + +link doink + +EOF +' + +test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x' +test_expect_success 'enable broken symlink workaround' \ + '(cd x && git config svn.brokenSymlinkWorkaround true)' +test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar' +test_expect_success 'get "bar" => symlink fix from svn' \ + '(cd x && git svn rebase)' +test_expect_success '"bar" becomes a symlink' 'test -L x/bar' + + +test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y' +test_expect_success 'disable broken symlink workaround' \ + '(cd y && git config svn.brokenSymlinkWorkaround false)' +test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar' +test_expect_success 'get "bar" => symlink fix from svn' \ + '(cd y && git svn rebase)' +test_expect_success '"bar" does not become a symlink' '! test -L y/bar' + +# svn.brokenSymlinkWorkaround is unset +test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" z' +test_expect_success '"bar" is an empty file' 'test -f z/bar && ! test -s z/bar' +test_expect_success 'get "bar" => symlink fix from svn' \ + '(cd z && git svn rebase)' +test_expect_success '"bar" does not become a symlink' '! test -L z/bar' + + +test_done diff --git a/t/t9132-git-svn-broken-symlink.sh b/t/t9132-git-svn-broken-symlink.sh new file mode 100755 index 0000000000..b8de59e493 --- /dev/null +++ b/t/t9132-git-svn-broken-symlink.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +test_description='test that git handles an svn repository with empty symlinks' + +. ./lib-git-svn.sh +test_expect_success 'load svn dumpfile' ' + svnadmin load "$rawsvnrepo" <<EOF +SVN-fs-dump-format-version: 2 + +UUID: 60780f9a-7df5-43b4-83ab-60e2c0673ef7 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2008-11-26T07:17:27.590577Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 4 +test +K 10 +svn:author +V 12 +normalperson +K 8 +svn:date +V 27 +2008-11-26T07:18:03.511836Z +PROPS-END + +Node-path: bar +Node-kind: file +Node-action: add +Prop-content-length: 33 +Text-content-length: 4 +Text-content-md5: 912ec803b2ce49e4a541068d495ab570 +Content-length: 37 + +K 11 +svn:special +V 1 +* +PROPS-END +asdf + +Revision-number: 2 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 13 +bar => doink + +K 10 +svn:author +V 12 +normalperson +K 8 +svn:date +V 27 +2008-11-27T03:55:31.601672Z +PROPS-END + +Node-path: bar +Node-kind: file +Node-action: change +Text-content-length: 10 +Text-content-md5: 92ca4fe7a9721f877f765c252dcd66c9 +Content-length: 10 + +link doink + +EOF +' + +test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x' + +test_expect_success '"bar" is a symlink that points to "asdf"' ' + test -L x/bar && + (cd x && test xasdf = x"`git cat-file blob HEAD:bar`") +' + +test_expect_success 'get "bar" => symlink fix from svn' ' + (cd x && git svn rebase) +' + +test_expect_success '"bar" remains a proper symlink' ' + test -L x/bar && + (cd x && test xdoink = x"`git cat-file blob HEAD:bar`") +' + +test_done diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh new file mode 100755 index 0000000000..893f57ef73 --- /dev/null +++ b/t/t9133-git-svn-nested-git-repo.sh @@ -0,0 +1,101 @@ +#!/bin/sh +# +# Copyright (c) 2009 Eric Wong +# + +test_description='git svn property tests' +. ./lib-git-svn.sh + +test_expect_success 'setup repo with a git repo inside it' ' + svn co "$svnrepo" s && + ( + cd s && + git init && + test -f .git/HEAD && + > .git/a && + echo a > a && + svn add .git a && + svn commit -m "create a nested git repo" && + svn up && + echo hi >> .git/a && + svn commit -m "modify .git/a" && + svn up + ) +' + +test_expect_success 'clone an SVN repo containing a git repo' ' + git svn clone "$svnrepo" g && + echo a > expect && + test_cmp expect g/a +' + +test_expect_success 'SVN-side change outside of .git' ' + ( + cd s && + echo b >> a && + svn commit -m "SVN-side change outside of .git" && + svn up && + svn log -v | fgrep "SVN-side change outside of .git" + ) +' + +test_expect_success 'update git svn-cloned repo' ' + ( + cd g && + git svn rebase && + echo a > expect && + echo b >> expect && + test_cmp a expect && + rm expect + ) +' + +test_expect_success 'SVN-side change inside of .git' ' + ( + cd s && + git add a && + git commit -m "add a inside an SVN repo" && + git log && + svn add --force .git && + svn commit -m "SVN-side change inside of .git" && + svn up && + svn log -v | fgrep "SVN-side change inside of .git" + ) +' + +test_expect_success 'update git svn-cloned repo' ' + ( + cd g && + git svn rebase && + echo a > expect && + echo b >> expect && + test_cmp a expect && + rm expect + ) +' + +test_expect_success 'SVN-side change in and out of .git' ' + ( + cd s && + echo c >> a && + git add a && + git commit -m "add a inside an SVN repo" && + svn commit -m "SVN-side change in and out of .git" && + svn up && + svn log -v | fgrep "SVN-side change in and out of .git" + ) +' + +test_expect_success 'update git svn-cloned repo again' ' + ( + cd g && + git svn rebase && + echo a > expect && + echo b >> expect && + echo c >> expect && + test_cmp a expect && + rm expect + ) +' + +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 0000000000..c4b5b8bcf7 --- /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/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh new file mode 100755 index 0000000000..03705fa4ce --- /dev/null +++ b/t/t9135-git-svn-moved-branch-empty-file.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +test_description='test moved svn branch with missing empty files' + +. ./lib-git-svn.sh +test_expect_success 'load svn dumpfile' ' + svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9135/svn.dump" + ' + +test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x' + +test_expect_success 'test that b1 exists and is empty' ' + (cd x && test -f b1 && ! test -s b1) + ' + +test_done diff --git a/t/t9135/svn.dump b/t/t9135/svn.dump new file mode 100644 index 0000000000..b51c0ccceb --- /dev/null +++ b/t/t9135/svn.dump @@ -0,0 +1,192 @@ +SVN-fs-dump-format-version: 2 + +UUID: 1f80e919-e9e3-4d80-a3ae-d9f21095e27b + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-02-10T19:23:16.424027Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 123 +Content-length: 123 + +K 7 +svn:log +V 20 +init standard layout +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:17.195072Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 121 +Content-length: 121 + +K 7 +svn:log +V 18 +branch-b off trunk +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:19.160095Z +PROPS-END + +Node-path: branches/branch-b +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk +Prop-content-length: 34 +Content-length: 34 + +K 13 +svn:mergeinfo +V 0 + +PROPS-END + + +Revision-number: 3 +Prop-content-length: 120 +Content-length: 120 + +K 7 +svn:log +V 17 +add empty file b1 +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:20.194568Z +PROPS-END + +Node-path: branches/branch-b/b1 +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Content-length: 10 + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 8 +branch-c +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:21.169100Z +PROPS-END + +Node-path: branches/branch-c +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk + + +Revision-number: 5 +Prop-content-length: 126 +Content-length: 126 + +K 7 +svn:log +V 23 +oops, wrong branchpoint +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:21.253557Z +PROPS-END + +Node-path: branches/branch-c +Node-action: delete + + +Revision-number: 6 +Prop-content-length: 127 +Content-length: 127 + +K 7 +svn:log +V 24 +branch-c off of branch-b +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-10T19:23:21.314659Z +PROPS-END + +Node-path: branches/branch-c +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/branch-b +Prop-content-length: 34 +Content-length: 34 + +K 13 +svn:mergeinfo +V 0 + +PROPS-END + + diff --git a/t/t9136-git-svn-recreated-branch-empty-file.sh b/t/t9136-git-svn-recreated-branch-empty-file.sh new file mode 100755 index 0000000000..733d16e0b2 --- /dev/null +++ b/t/t9136-git-svn-recreated-branch-empty-file.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +test_description='test recreated svn branch with empty files' + +. ./lib-git-svn.sh +test_expect_success 'load svn dumpfile' ' + svnadmin load "$rawsvnrepo" < "${TEST_DIRECTORY}/t9136/svn.dump" + ' + +test_expect_success 'clone using git svn' 'git svn clone -s "$svnrepo" x' + +test_done diff --git a/t/t9136/svn.dump b/t/t9136/svn.dump new file mode 100644 index 0000000000..6b1ce0b2e8 --- /dev/null +++ b/t/t9136/svn.dump @@ -0,0 +1,192 @@ +SVN-fs-dump-format-version: 2 + +UUID: eecae021-8f16-48da-969d-79beb8ae6ea5 + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2009-02-22T00:50:56.292890Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 4 +init +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-22T00:50:57.192384Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/file +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 0 +Text-content-md5: d41d8cd98f00b204e9800998ecf8427e +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 105 +Content-length: 105 + +K 7 +svn:log +V 3 +1.0 +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-22T00:50:58.124724Z +PROPS-END + +Node-path: tags/1.0 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Revision-number: 3 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 9 +1.0.1-bad +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-22T00:50:58.151727Z +PROPS-END + +Node-path: tags/1.0.1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: tags/1.0 + + +Revision-number: 4 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 9 +Wrong tag +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-22T00:50:58.167427Z +PROPS-END + +Node-path: tags/1.0.1 +Node-action: delete + + +Revision-number: 5 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 10 +1.0-branch +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-22T00:50:58.184498Z +PROPS-END + +Node-path: branches/1.0 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 4 +Node-copyfrom-path: tags/1.0 + + +Revision-number: 6 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 10 +1.0.1-good +K 10 +svn:author +V 8 +john.doe +K 8 +svn:date +V 27 +2009-02-22T00:50:58.200695Z +PROPS-END + +Node-path: tags/1.0.1 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/1.0 + + diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh index 86c376088c..4a87f36258 100755 --- a/t/t9301-fast-export.sh +++ b/t/t9301-fast-export.sh @@ -259,4 +259,19 @@ test_expect_success 'cope with tagger-less tags' ' ' +test_expect_success 'set-up a few more tags for tag export tests' ' + git checkout -f master && + HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` && + git tag tree_tag -m "tagging a tree" $HEAD_TREE && + git tag -a tree_tag-obj -m "tagging a tree" $HEAD_TREE && + git tag tag-obj_tag -m "tagging a tag" tree_tag-obj && + git tag -a tag-obj_tag-obj -m "tagging a tag" tree_tag-obj +' + +# NEEDSWORK: not just check return status, but validate the output +test_expect_success 'tree_tag' 'git fast-export tree_tag' +test_expect_success 'tree_tag-obj' 'git fast-export tree_tag-obj' +test_expect_success 'tag-obj_tag' 'git fast-export tag-obj_tag' +test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj' + test_done diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 43cd6eecba..6ed10d0933 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -43,9 +43,11 @@ gitweb_run () { GATEWAY_INTERFACE="CGI/1.1" HTTP_ACCEPT="*/*" REQUEST_METHOD="GET" + SCRIPT_NAME="$TEST_DIRECTORY/../gitweb/gitweb.perl" QUERY_STRING=""$1"" PATH_INFO=""$2"" - export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD QUERY_STRING PATH_INFO + export GATEWAY_INTERFACE HTTP_ACCEPT REQUEST_METHOD \ + SCRIPT_NAME QUERY_STRING PATH_INFO GITWEB_CONFIG=$(pwd)/gitweb_config.perl export GITWEB_CONFIG @@ -54,7 +56,7 @@ gitweb_run () { # written to web server logs, so we are not interested in that: # we are interested only in properly formatted errors/warnings rm -f gitweb.log && - perl -- "$TEST_DIRECTORY/../gitweb/gitweb.perl" \ + perl -- "$SCRIPT_NAME" \ >/dev/null 2>gitweb.log && if grep "^[[]" gitweb.log >/dev/null 2>&1; then false; else true; fi @@ -660,6 +662,11 @@ cat >>gitweb_config.perl <<EOF EOF test_expect_success \ + 'config override: tree view, features not overridden in repo config' \ + 'gitweb_run "p=.git;a=tree"' +test_debug 'cat gitweb.log' + +test_expect_success \ 'config override: tree view, features disabled in repo config' \ 'git config gitweb.blame no && git config gitweb.snapshot none && @@ -667,12 +674,23 @@ test_expect_success \ test_debug 'cat gitweb.log' test_expect_success \ - 'config override: tree view, features enabled in repo config' \ + 'config override: tree view, features enabled in repo config (1)' \ 'git config gitweb.blame yes && git config gitweb.snapshot "zip,tgz, tbz2" && gitweb_run "p=.git;a=tree"' test_debug 'cat gitweb.log' +cat >.git/config <<\EOF +# testing noval and alternate separator +[gitweb] + blame + snapshot = zip tgz +EOF +test_expect_success \ + 'config override: tree view, features enabled in repo config (2)' \ + 'gitweb_run "p=.git;a=tree"' +test_debug 'cat gitweb.log' + # ---------------------------------------------------------------------- # non-ASCII in README.html diff --git a/t/test-lib.sh b/t/test-lib.sh index 1b4f216385..59d82d25e9 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -127,7 +127,7 @@ fi error () { say_color error "error: $*" - trap - exit + trap - EXIT exit 1 } @@ -163,7 +163,7 @@ die () { exit 1 } -trap 'die' exit +trap 'die' EXIT # The semantics of the editor variables are that of invoking # sh -c "$EDITOR \"$@\"" files ... @@ -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. @@ -208,7 +233,7 @@ test_failure_ () { say_color error "FAIL $test_count: $1" shift echo "$@" | sed -e 's/^/ /' - test "$immediate" = "" || { trap - exit; exit 1; } + test "$immediate" = "" || { trap - EXIT; exit 1; } } test_known_broken_ok_ () { @@ -416,7 +441,7 @@ test_create_repo () { } test_done () { - trap - exit + trap - EXIT test_results_dir="$TEST_DIRECTORY/test-results" mkdir -p "$test_results_dir" test_results_path="$test_results_dir/${0%-*}-$$" @@ -485,7 +510,7 @@ fi test="trash directory.$(basename "$0" .sh)" test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test" rm -fr "$test" || { - trap - exit + trap - EXIT echo >&5 "FATAL: Cannot prepare test area" exit 1 } diff --git a/test-ctype.c b/test-ctype.c new file mode 100644 index 0000000000..033c74911e --- /dev/null +++ b/test-ctype.c @@ -0,0 +1,78 @@ +#include "cache.h" + + +static int test_isdigit(int c) +{ + return isdigit(c); +} + +static int test_isspace(int c) +{ + return isspace(c); +} + +static int test_isalpha(int c) +{ + return isalpha(c); +} + +static int test_isalnum(int c) +{ + return isalnum(c); +} + +static int test_is_glob_special(int c) +{ + return is_glob_special(c); +} + +static int test_is_regex_special(int c) +{ + return is_regex_special(c); +} + +#define DIGIT "0123456789" +#define LOWER "abcdefghijklmnopqrstuvwxyz" +#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +static const struct ctype_class { + const char *name; + int (*test_fn)(int); + const char *members; +} classes[] = { + { "isdigit", test_isdigit, DIGIT }, + { "isspace", test_isspace, " \n\r\t" }, + { "isalpha", test_isalpha, LOWER UPPER }, + { "isalnum", test_isalnum, LOWER UPPER DIGIT }, + { "is_glob_special", test_is_glob_special, "*?[\\" }, + { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" }, + { NULL } +}; + +static int test_class(const struct ctype_class *test) +{ + int i, rc = 0; + + for (i = 0; i < 256; i++) { + int expected = i ? !!strchr(test->members, i) : 0; + int actual = test->test_fn(i); + + if (actual != expected) { + rc = 1; + printf("%s classifies char %d (0x%02x) wrongly\n", + test->name, i, i); + } + } + return rc; +} + +int main(int argc, char **argv) +{ + const struct ctype_class *test; + int rc = 0; + + for (test = classes; test->name; test++) + rc |= test_class(test); + + return rc; +} diff --git a/test-path-utils.c b/test-path-utils.c index 5168a8e3df..d261398d6c 100644 --- a/test-path-utils.c +++ b/test-path-utils.c @@ -26,6 +26,12 @@ int main(int argc, char **argv) return 0; } + if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) { + char *prefix = strip_path_suffix(argv[2], argv[3]); + printf("%s\n", prefix ? prefix : "(null)"); + return 0; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/test-sigchain.c b/test-sigchain.c new file mode 100644 index 0000000000..42db234e87 --- /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; +} @@ -50,7 +50,7 @@ static int get_trace_fd(int *need_close) return fd; } - fprintf(stderr, "What does '%s' for GIT_TRACE means ?\n", trace); + fprintf(stderr, "What does '%s' for GIT_TRACE mean?\n", trace); fprintf(stderr, "If you want to trace into a file, " "then please set GIT_TRACE to an absolute pathname " "(starting with /).\n"); diff --git a/transport.c b/transport.c index 56831c57c5..9ae92cd39c 100644 --- a/transport.c +++ b/transport.c @@ -50,9 +50,7 @@ static int read_loose_refs(struct strbuf *path, int name_offset, memset (&list, 0, sizeof(list)); while ((de = readdir(dir))) { - if (de->d_name[0] == '.' && (de->d_name[1] == '\0' || - (de->d_name[1] == '.' && - de->d_name[2] == '\0'))) + if (is_dot_or_dotdot(de->d_name)) continue; ALLOC_GROW(list.entries, list.nr + 1, list.alloc); list.entries[list.nr++] = xstrdup(de->d_name); @@ -140,6 +138,11 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list) } } +static const char *rsync_url(const char *url) +{ + return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url; +} + static struct ref *get_refs_via_rsync(struct transport *transport) { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; @@ -155,7 +158,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport) die ("Could not make temporary directory"); temp_dir_len = temp_dir.len; - strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addstr(&buf, "/refs"); memset(&rsync, 0, sizeof(rsync)); @@ -171,7 +174,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport) die ("Could not run rsync to get refs"); strbuf_reset(&buf); - strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addstr(&buf, "/packed-refs"); args[2] = buf.buf; @@ -208,7 +211,7 @@ static int fetch_objs_via_rsync(struct transport *transport, const char *args[8]; int result; - strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addstr(&buf, "/objects/"); memset(&rsync, 0, sizeof(rsync)); @@ -287,7 +290,7 @@ static int rsync_transport_push(struct transport *transport, /* first push the objects */ - strbuf_addstr(&buf, transport->url); + strbuf_addstr(&buf, rsync_url(transport->url)); strbuf_addch(&buf, '/'); memset(&rsync, 0, sizeof(rsync)); @@ -308,7 +311,8 @@ static int rsync_transport_push(struct transport *transport, args[i++] = NULL; if (run_command(&rsync)) - return error("Could not push objects to %s", transport->url); + return error("Could not push objects to %s", + rsync_url(transport->url)); /* copy the refs to the temporary directory; they could be packed. */ @@ -329,10 +333,11 @@ static int rsync_transport_push(struct transport *transport, if (!(flags & TRANSPORT_PUSH_FORCE)) args[i++] = "--ignore-existing"; args[i++] = temp_dir.buf; - args[i++] = transport->url; + args[i++] = rsync_url(transport->url); args[i++] = NULL; if (run_command(&rsync)) - result = error("Could not push to %s", transport->url); + result = error("Could not push to %s", + rsync_url(transport->url)); if (remove_dir_recursively(&temp_dir, 0)) warning ("Could not remove temporary directory %s.", @@ -725,7 +730,7 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->remote = remote; ret->url = url; - if (!prefixcmp(url, "rsync://")) { + if (!prefixcmp(url, "rsync:")) { ret->get_refs_list = get_refs_via_rsync; ret->fetch = fetch_objs_via_rsync; ret->push = rsync_transport_push; @@ -62,6 +62,7 @@ static int match_tree_entry(const char *base, int baselen, const char *path, uns continue; /* pathspecs match only at the directory boundaries */ if (!matchlen || + baselen == matchlen || base[matchlen] == '/' || match[matchlen - 1] == '/') return 1; @@ -114,7 +115,7 @@ int read_tree_recursive(struct tree *tree, case 0: continue; case READ_TREE_RECURSIVE: - break;; + break; default: return -1; } @@ -135,6 +136,34 @@ int read_tree_recursive(struct tree *tree, if (retval) return -1; continue; + } else if (S_ISGITLINK(entry.mode)) { + int retval; + struct strbuf path; + unsigned int entrylen; + struct commit *commit; + + entrylen = tree_entry_len(entry.path, entry.sha1); + strbuf_init(&path, baselen + entrylen + 1); + strbuf_add(&path, base, baselen); + strbuf_add(&path, entry.path, entrylen); + strbuf_addch(&path, '/'); + + commit = lookup_commit(entry.sha1); + if (!commit) + die("Commit %s in submodule path %s not found", + sha1_to_hex(entry.sha1), path.buf); + + if (parse_commit(commit)) + die("Invalid commit %s in submodule path %s", + sha1_to_hex(entry.sha1), path.buf); + + retval = read_tree_recursive(commit->tree, + path.buf, path.len, + stage, match, fn, context); + strbuf_release(&path); + if (retval) + return -1; + continue; } } return 0; diff --git a/unpack-file.c b/unpack-file.c index bcdc8bbb3b..75cd2f1a6a 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 3a4e181af4..5820ce48f3 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -49,7 +49,7 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, memcpy(new, ce, size); new->next = NULL; new->ce_flags = (new->ce_flags & ~clear) | set; - add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK); + add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); } /* Unlink the last component and attempt to remove leading @@ -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; @@ -286,9 +286,9 @@ static int unpack_nondirectories(int n, unsigned long mask, if (o->merge) return call_unpack_fn(src, o); - n += o->merge; for (i = 0; i < n; i++) - add_entry(o, src[i], 0, 0); + if (src[i] && src[i] != o->df_conflict_entry) + add_entry(o, src[i], 0, 0); return 0; } @@ -583,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 7e8209ea4b..7b38fd867b 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 e5adbc011e..b98b1d61c1 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) @@ -78,20 +78,22 @@ static void show_commit(struct commit *commit) commit->buffer = NULL; } -static void show_object(struct object_array_entry *p) +static void show_object(struct object *obj, const struct name_path *path, const char *component) { /* An object with name "foo\n0000000..." can be used to * confuse downstream git-pack-objects very badly. */ - const char *ep = strchr(p->name, '\n'); + const char *name = path_name(path, component); + const char *ep = strchr(name, '\n'); if (ep) { - fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(p->item->sha1), - (int) (ep - p->name), - p->name); + fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1), + (int) (ep - name), + name); } else fprintf(pack_pipe, "%s %s\n", - sha1_to_hex(p->item->sha1), p->name); + sha1_to_hex(obj->sha1), name); + free((char *)name); } static void show_edge(struct commit *commit) @@ -616,6 +618,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]; @@ -7,7 +7,7 @@ static void report(const char *prefix, const char *err, va_list params) { - char msg[256]; + char msg[1024]; vsnprintf(msg, sizeof(msg), err, params); fprintf(stderr, "%s%s\n", prefix, msg); } diff --git a/userdiff.c b/userdiff.c index 3681062ebf..d556da9751 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 ba2945770b..c3151594f5 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; @@ -18,7 +18,7 @@ void walker_say(struct walker *walker, const char *fmt, const char *hex) static void report_missing(const struct object *obj) { char missing_hex[41]; - strcpy(missing_hex, sha1_to_hex(obj->sha1));; + strcpy(missing_hex, sha1_to_hex(obj->sha1)); fprintf(stderr, "Cannot obtain needed %s %s\n", obj->type ? typename(obj->type): "object", missing_hex); if (!is_null_sha1(current_commit_sha1)) diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 84fff583e2..4da052a3ff 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 '-' @@ -84,6 +85,7 @@ typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long typedef struct s_xdemitconf { long ctxlen; + long interhunkctxlen; unsigned long flags; find_func_t find_func; void *find_func_priv; diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 9d0324a38c..3e97462bdd 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 3e099dc445..ad033a8e6a 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/xemit.c b/xdiff/xemit.c index 4625c1b421..05bfa41f10 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -59,9 +59,10 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * */ xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { xdchange_t *xch, *xchp; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) - if (xch->i1 - (xchp->i1 + xchp->chg1) > 2 * xecfg->ctxlen) + if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) break; return xchp; diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c new file mode 100644 index 0000000000..e42c16a807 --- /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 a43aa72cd0..1689085235 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); |