diff options
516 files changed, 27171 insertions, 8561 deletions
diff --git a/.gitignore b/.gitignore index 1c57d4c958..41c0b20a76 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ git-apply git-archimport git-archive git-bisect +git-bisect--helper git-blame git-branch git-bundle @@ -35,6 +36,8 @@ git-diff git-diff-files git-diff-index git-diff-tree +git-difftool +git-difftool--helper git-describe git-fast-export git-fast-import @@ -78,6 +81,7 @@ git-merge-recursive git-merge-resolve git-merge-subtree git-mergetool +git-mergetool--lib git-mktag git-mktree git-name-rev diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 0d7fa9cca9..b8bf618a30 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -129,3 +129,6 @@ For C programs: used in the git core command set (unless your command is clearly separate from it, such as an importer to convert random-scm-X repositories to git). + + - When we pass <string, length> pair to functions, we should try to + pass them in that order. diff --git a/Documentation/Makefile b/Documentation/Makefile index 144ec32f12..7a8037f586 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -41,7 +41,8 @@ man7dir=$(mandir)/man7 ASCIIDOC=asciidoc ASCIIDOC_EXTRA = -MANPAGE_XSL = callouts.xsl +MANPAGE_XSL = manpage-normal.xsl +XMLTO_EXTRA = INSTALL?=install RM ?= rm -f DOC_REF = origin/man @@ -59,13 +60,52 @@ endif -include ../config.mak.autogen -include ../config.mak +# +# For asciidoc ... +# -7.1.2, no extra settings are needed. +# 8.0-, set ASCIIDOC8. +# + +# +# For docbook-xsl ... +# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0) +# 1.69.0, no extra settings are needed? +# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP? +# 1.71.1, no extra settings are needed? +# 1.72.0, set DOCBOOK_XSL_172. +# 1.73.0-, set ASCIIDOC_NO_ROFF +# + +# +# If you had been using DOCBOOK_XSL_172 in an attempt to get rid +# of 'the ".ft C" problem' in your generated manpages, and you +# instead ended up with weird characters around callouts, try +# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8). +# + ifdef ASCIIDOC8 ASCIIDOC_EXTRA += -a asciidoc7compatible endif ifdef DOCBOOK_XSL_172 -ASCIIDOC_EXTRA += -a docbook-xsl-172 +ASCIIDOC_EXTRA += -a git-asciidoc-no-roff MANPAGE_XSL = manpage-1.72.xsl +else + ifdef ASCIIDOC_NO_ROFF + # docbook-xsl after 1.72 needs the regular XSL, but will not + # pass-thru raw roff codes from asciidoc.conf, so turn them off. + ASCIIDOC_EXTRA += -a git-asciidoc-no-roff + endif endif +ifdef MAN_BOLD_LITERAL +XMLTO_EXTRA += -m manpage-bold-literal.xsl +endif +ifdef DOCBOOK_SUPPRESS_SP +XMLTO_EXTRA += -m manpage-suppress-sp.xsl +endif + +SHELL_PATH ?= $(SHELL) +# Shell quote; +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) # # Please note that there is a minor bug in asciidoc. @@ -76,6 +116,32 @@ endif # yourself - yes, all 6 characters of it! # +QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir +QUIET_SUBDIR1 = + +ifneq ($(findstring $(MAKEFLAGS),w),w) +PRINT_DIR = --no-print-directory +else # "make -w" +NO_SUBDIR = : +endif + +ifneq ($(findstring $(MAKEFLAGS),s),s) +ifndef V + QUIET_ASCIIDOC = @echo ' ' ASCIIDOC $@; + QUIET_XMLTO = @echo ' ' XMLTO $@; + QUIET_DB2TEXI = @echo ' ' DB2TEXI $@; + QUIET_MAKEINFO = @echo ' ' MAKEINFO $@; + QUIET_DBLATEX = @echo ' ' DBLATEX $@; + QUIET_XSLTPROC = @echo ' ' XSLTPROC $@; + QUIET_GEN = @echo ' ' GEN $@; + QUIET_STDERR = 2> /dev/null + QUIET_SUBDIR0 = +@subdir= + QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ + $(MAKE) $(PRINT_DIR) -C $$subdir + export V +endif +endif + all: html man html: $(DOC_HTML) @@ -116,10 +182,10 @@ install-pdf: pdf $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir) install-html: html - sh ./install-webdoc.sh $(DESTDIR)$(htmldir) + '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir) ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE - $(MAKE) -C ../ GIT-VERSION-FILE + $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE -include ../GIT-VERSION-FILE @@ -127,8 +193,8 @@ install-html: html # Determine "include::" file references in asciidoc files. # doc.dep : $(wildcard *.txt) build-docdep.perl - $(RM) $@+ $@ - $(PERL_PATH) ./build-docdep.perl >$@+ + $(QUIET_GEN)$(RM) $@+ $@ && \ + $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \ mv $@+ $@ -include doc.dep @@ -146,102 +212,105 @@ cmds_txt = cmds-ancillaryinterrogators.txt \ $(cmds_txt): cmd-list.made cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT) - $(RM) $@ - $(PERL_PATH) ./cmd-list.perl ../command-list.txt + $(QUIET_GEN)$(RM) $@ && \ + $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \ date >$@ clean: $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 - $(RM) *.texi *.texi+ git.info gitman.info + $(RM) *.texi *.texi+ *.texi++ git.info gitman.info $(RM) howto-index.txt howto/*.html doc.dep $(RM) technical/api-*.html technical/api-index.txt $(RM) $(cmds_txt) *.made $(MAN_HTML): %.html : %.txt - $(RM) $@+ $@ + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \ - $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< + $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ mv $@+ $@ %.1 %.5 %.7 : %.xml - $(RM) $@ - xmlto -m $(MANPAGE_XSL) man $< + $(QUIET_XMLTO)$(RM) $@ && \ + xmlto -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< %.xml : %.txt - $(RM) $@+ $@ + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \ - $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< + $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \ mv $@+ $@ user-manual.xml: user-manual.txt user-manual.conf - $(ASCIIDOC) -b docbook -d book $< + $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d book $< technical/api-index.txt: technical/api-index-skel.txt \ technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS)) - cd technical && sh ./api-index.sh + $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh $(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt - $(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ + $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \ $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt XSLT = docbook.xsl XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css user-manual.html: user-manual.xml - xsltproc $(XSLTOPTS) -o $@ $(XSLT) $< + $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $< git.info: user-manual.texi - $(MAKEINFO) --no-split -o $@ user-manual.texi + $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi user-manual.texi: user-manual.xml - $(RM) $@+ $@ - $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout | \ - $(PERL_PATH) fix-texi.perl >$@+ + $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ + $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \ + $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \ + rm $@++ && \ mv $@+ $@ user-manual.pdf: user-manual.xml - $(RM) $@+ $@ - $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< + $(QUIET_DBLATEX)$(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) $@+ $@ + $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \ - --to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+ + --to-stdout $(xml) &&) true) > $@++ && \ + $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \ + rm $@++ && \ mv $@+ $@ gitman.info: gitman.texi - $(MAKEINFO) --no-split --no-validate $*.texi + $(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml - $(RM) $@+ $@ - $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ + $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ + $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \ mv $@+ $@ howto-index.txt: howto-index.sh $(wildcard howto/*.txt) - $(RM) $@+ $@ - sh ./howto-index.sh $(wildcard howto/*.txt) >$@+ + $(QUIET_GEN)$(RM) $@+ $@ && \ + '$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \ mv $@+ $@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt - $(ASCIIDOC) -b xhtml11 $*.txt + $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 $*.txt WEBDOC_DEST = /pub/software/scm/git/docs $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt - $(RM) $@+ $@ - sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+ + $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ + sed -e '1,/^$$/d' $< | $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 - >$@+ && \ mv $@+ $@ install-webdoc : html - sh ./install-webdoc.sh $(WEBDOC_DEST) + '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST) quick-install: quick-install-man quick-install-man: - sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir) + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir) quick-install-html: - sh ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir) + '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir) .PHONY: .FORCE-GIT-VERSION-FILE diff --git a/Documentation/RelNotes-1.6.3.1.txt b/Documentation/RelNotes-1.6.3.1.txt new file mode 100644 index 0000000000..2400b72ef7 --- /dev/null +++ b/Documentation/RelNotes-1.6.3.1.txt @@ -0,0 +1,10 @@ +GIT v1.6.3.1 Release Notes +========================== + +Fixes since v1.6.3 +------------------ + +* "git checkout -b new-branch" with a staged change in the index + incorrectly primed the in-index cache-tree, resulting a wrong tree + object to be written out of the index. This is a grave regression + since the last 1.6.2.X maintenance release. diff --git a/Documentation/RelNotes-1.6.3.2.txt b/Documentation/RelNotes-1.6.3.2.txt new file mode 100644 index 0000000000..b2f3f0293c --- /dev/null +++ b/Documentation/RelNotes-1.6.3.2.txt @@ -0,0 +1,61 @@ +GIT v1.6.3.2 Release Notes +========================== + +Fixes since v1.6.3.1 +-------------------- + + * A few codepaths picked up the first few bytes from an sha1[] by + casting the (char *) pointer to (int *); GCC 4.4 did not like this, + and aborted compilation. + + * Some unlink(2) failures went undiagnosed. + + * The "recursive" merge strategy misbehaved when faced rename/delete + conflicts while coming up with an intermediate merge base. + + * The low-level merge algorithm did not handle a degenerate case of + merging a file with itself using itself as the common ancestor + gracefully. It should produce the file itself, but instead + produced an empty result. + + * GIT_TRACE mechanism segfaulted when tracing a shell-quoted aliases. + + * OpenBSD also uses st_ctimspec in "struct stat", instead of "st_ctim". + + * With NO_CROSS_DIRECTORY_HARDLINKS, "make install" can be told not to + create hardlinks between $(gitexecdir)/git-$builtin_commands and + $(bindir)/git. + + * command completion code in bash did not reliably detect that we are + in a bare repository. + + * "git add ." in an empty directory complained that pathspec "." did not + match anything, which may be technically correct, but not useful. We + silently make it a no-op now. + + * "git add -p" (and "patch" action in "git add -i") was broken when + the first hunk that adds a line at the top was split into two and + both halves are marked to be used. + + * "git blame path" misbehaved at the commit where path became file + from a directory with some files in it. + + * "git for-each-ref" had a segfaulting bug when dealing with a tag object + created by an ancient git. + + * "git format-patch -k" still added patch numbers if format.numbered + configuration was set. + + * "git grep --color ''" did not terminate. The command also had + subtle bugs with its -w option. + + * http-push had a small use-after-free bug. + + * "git push" was converting OFS_DELTA pack representation into less + efficient REF_DELTA representation unconditionally upon transfer, + making the transferred data unnecessarily larger. + + * "git remote show origin" segfaulted when origin was still empty. + +Many other general usability updates around help text, diagnostic messages +and documentation are included as well. diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt new file mode 100644 index 0000000000..418c685cf8 --- /dev/null +++ b/Documentation/RelNotes-1.6.3.txt @@ -0,0 +1,182 @@ +GIT v1.6.3 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. + +When the user does not tell "git push" what to push, it has always +pushed matching refs. For some people it is unexpected, and a new +configuration variable push.default has been introduced to allow +changing a different default behaviour. To advertise the new feature, +a big warning is issued if this is not configured and a git push without +arguments is attempted. + + +Updates since v1.6.2 +-------------------- + +(subsystems) + +* various git-svn updates. + +* git-gui updates, including an update to Russian translation, and a + fix to an infinite loop when showing an empty diff. + +* gitk updates, including an update to Russian translation and improved Windows + support. + +(performance) + +* many uses of lstat(2) in the codepath for "git checkout" have been + optimized out. + +(usability, bells and whistles) + +* Boolean configuration variable yes/no can be written as on/off. + +* rsync:/path/to/repo can be used to run git over rsync for local + repositories. It may not be useful in practice; meant primarily for + testing. + +* http transport learned to prompt and use password when fetching from or + pushing to http://user@host.xz/ URL. + +* (msysgit) progress output that is sent over the sideband protocol can + be handled appropriately in Windows console. + +* "--pretty=<style>" option to the log family of commands can now be + spelled as "--format=<style>". In addition, --format=%formatstring + is a short-hand for --pretty=tformat:%formatstring. + +* "--oneline" is a synonym for "--pretty=oneline --abbrev-commit". + +* "--graph" to the "git log" family can draw the commit ancestry graph + in colors. + +* If you realize that you botched the patch when you are editing hunks + with the 'edit' action in git-add -i/-p, you can abort the editor to + tell git not to apply it. + +* @{-1} is a new way to refer to the last branch you were on introduced in + 1.6.2, but the initial implementation did not teach this to a few + commands. Now the syntax works with "branch -m @{-1} newname". + +* git-archive learned --output=<file> option. + +* git-archive takes attributes from the tree being archived; strictly + speaking, this is an incompatible behaviour change, but is a good one. + Use --worktree-attributes option to allow it to read attributes from + the work tree as before (deprecated git-tar tree command always reads + attributes from the work tree). + +* git-bisect shows not just the number of remaining commits whose goodness + is unknown, but also shows the estimated number of remaining rounds. + +* You can give --date=<format> option to git-blame. + +* "git-branch -r" shows HEAD symref that points at a remote branch in + interest of each tracked remote repository. + +* "git-branch -v -v" is a new way to get list of names for branches and the + "upstream" branch for them. + +* git-config learned -e option to open an editor to edit the config file + directly. + +* git-clone runs post-checkout hook when run without --no-checkout. + +* git-difftool is now part of the officially supported command, primarily + maintained by David Aguilar. + +* git-for-each-ref learned a new "upstream" token. + +* git-format-patch can be told to use attachment with a new configuration, + format.attach. + +* git-format-patch can be told to produce deep or shallow message threads. + +* git-format-patch can be told to always add sign-off with a configuration + variable. + +* git-format-patch learned format.headers configuration to add extra + header fields to the output. This behaviour is similar to the existing + --add-header=<header> option of the command. + +* git-format-patch gives human readable names to the attached files, when + told to send patches as attachments. + +* git-grep learned to highlight the found substrings in color. + +* git-imap-send learned to work around Thunderbird's inability to easily + disable format=flowed with a new configuration, imap.preformattedHTML. + +* git-rebase can be told to rebase the series even if your branch is a + descendant of the commit you are rebasing onto with --force-rebase + option. + +* git-rebase can be told to report diffstat with the --stat option. + +* Output from git-remote command has been vastly improved. + +* "git remote update --prune $remote" updates from the named remote and + then prunes stale tracking branches. + +* git-send-email learned --confirm option to review the Cc: list before + sending the messages out. + +(developers) + +* Test scripts can be run under valgrind. + +* Test scripts can be run with installed git. + +* Makefile learned 'coverage' option to run the test suites with + coverage tracking enabled. + +* Building the manpages with docbook-xsl between 1.69.1 and 1.71.1 now + requires setting DOCBOOK_SUPPRESS_SP to work around a docbook-xsl bug. + This workaround used to be enabled by default, but causes problems + with newer versions of docbook-xsl. In addition, there are a few more + knobs you can tweak to work around issues with various versions of the + docbook-xsl package. See comments in Documentation/Makefile for details. + +* Support for building and testing a subset of git on a system without a + working perl has been improved. + + +Fixes since v1.6.2 +------------------ + +All of the fixes in v1.6.2.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.2.X series. + +* "git-apply" rejected a patch that swaps two files (i.e. renames A to B + and B to A at the same time). May need to be backported by cherry + picking d8c81df and then 7fac0ee). + +* The initial checkout did not read the attributes from the .gitattribute + file that is being checked out. + +* git-gc spent excessive amount of time to decide if an object appears + in a locally existing pack (if needed, backport by merging 69e020a). diff --git a/Documentation/RelNotes-1.6.4.txt b/Documentation/RelNotes-1.6.4.txt new file mode 100644 index 0000000000..af68297af5 --- /dev/null +++ b/Documentation/RelNotes-1.6.4.txt @@ -0,0 +1,93 @@ +GIT v1.6.4 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. + +When the user does not tell "git push" what to push, it has always +pushed matching refs. For some people it is unexpected, and a new +configuration variable push.default has been introduced to allow +changing a different default behaviour. To advertise the new feature, +a big warning is issued if this is not configured and a git push without +arguments is attempted. + + +Updates since v1.6.3 +-------------------- + +(subsystems) + + * gitweb Perl style clean-up. + + * git-svn updates, including a new --authors-prog option to map author + names by invoking an external program. + +(portability) + + * We feed iconv with "UTF-8" instead of "utf8"; the former is + understood more widely. + +(performance) + +(usability, bells and whistles) + + * "git add --edit" lets users edit the whole patch text to fine-tune what + is added to the index. + + * "git log --graph" draws graphs more compactly by using horizonal lines + when able. + + * "git log --decorate" shows shorter refnames by stripping well-known + refs/* prefix. + + * "git send-email" understands quoted aliases in .mailrc files (might + have to be backported to 1.6.3.X). + + * "git send-email" can fetch the sender address from the configuration + variable "sendmail.from" (and "sendmail.<identity>.from"). + + * "git show-branch" can color its output. + + * "add" and "update" subcommands to "git submodule" learned --reference + option to use local clone with references. + +(developers) + + * A major part of the "git bisect" wrapper has moved to C. + +Fixes since v1.6.3 +------------------ + +All of the fixes in v1.6.3.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.3.X series. + + * The way Git.pm sets up a Repository object was not friendly to callers + that chdir around. It now internally records the repository location + as an absolute path when autodetected. + +--- +exec >/var/tmp/1 +echo O=$(git describe master) +O=v1.6.3.1-168-g23807fa +git shortlog --no-merges $O..master ^maint diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 9b559adefc..76fc84d878 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -6,9 +6,13 @@ Checklist (and a short version for the impatient): - check for unnecessary whitespace with "git diff --check" before committing - do not check in commented out code or unneeded files - - provide a meaningful commit message - the first line of the commit message should be a short description and should skip the full stop + - the body should provide a meaningful commit message, which: + - uses the imperative, present tense: "change", + not "changed" or "changes". + - includes motivation for the change, and contrasts + its implementation with previous behaviour - if you want your work included in git.git, add a "Signed-off-by: Your Name <you@example.com>" line to the commit message (or just use the option "-s" when @@ -62,6 +66,14 @@ Describe the technical detail of the change(s). If your description starts to get too long, that's a sign that you probably need to split up your commit to finer grained pieces. +That being said, patches which plainly describe the things that +help reviewers check the patch, and future maintainers understand +the code, are the most beautiful patches. Descriptions that summarise +the point in the subject well, and describe the motivation for the +change, the approach taken by the change, and if relevant how this +differs substantially from the prior version, can be found on Usenet +archives back into the late 80's. Consider it like good Netiquette, +but for code. Oh, another thing. I am picky about whitespaces. Make sure your changes do not trigger errors with the sample pre-commit hook shipped @@ -491,6 +503,12 @@ message, complete the addressing and subject fields, and press send. Gmail ----- +GMail does not appear to have any way to turn off line wrapping in the web +interface, so this will mangle any emails that you send. You can however +use any IMAP email client to connect to the google imap server, and forward +the emails through that. Just make sure to disable line wrapping in that +email client. Alternatively, use "git send-email" instead. + Submitting properly formatted patches via Gmail is simple now that IMAP support is available. First, edit your ~/.gitconfig to specify your account settings: @@ -503,6 +521,9 @@ account settings: port = 993 sslverify = false +You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error +that the "Folder doesn't exist". + Next, ensure that your Gmail settings are correct. In "Settings" the "Use Unicode (UTF-8) encoding for outgoing messages" should be checked. @@ -513,3 +534,4 @@ command to send the patch emails to your Gmail Drafts folder. Go to your Gmail account, open the Drafts folder, find the patch email, fill in the To: and CC: fields and send away! + diff --git a/Documentation/asciidoc.conf b/Documentation/asciidoc.conf index 1e735df3bb..dc76e7f073 100644 --- a/Documentation/asciidoc.conf +++ b/Documentation/asciidoc.conf @@ -27,7 +27,7 @@ ifdef::backend-docbook[] endif::backend-docbook[] ifdef::backend-docbook[] -ifndef::docbook-xsl-172[] +ifndef::git-asciidoc-no-roff[] # "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this. # v1.72 breaks with this because it replaces dots not in roff requests. [listingblock] @@ -42,16 +42,16 @@ ifdef::doctype-manpage[] endif::doctype-manpage[] </literallayout> {title#}</example> -endif::docbook-xsl-172[] +endif::git-asciidoc-no-roff[] -ifdef::docbook-xsl-172[] +ifdef::git-asciidoc-no-roff[] ifdef::doctype-manpage[] # The following two small workarounds insert a simple paragraph after screen [listingblock] <example><title>{title}</title> -<screen> +<literallayout> | -</screen><simpara></simpara> +</literallayout><simpara></simpara> {title#}</example> [verseblock] @@ -59,10 +59,11 @@ ifdef::doctype-manpage[] {title%}<literallayout{id? id="{id}"}> {title#}<literallayout> | -</literallayout><simpara></simpara> +</literallayout> {title#}</para></formalpara> +{title%}<simpara></simpara> endif::doctype-manpage[] -endif::docbook-xsl-172[] +endif::git-asciidoc-no-roff[] endif::backend-docbook[] ifdef::doctype-manpage[] diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index 26cfb62195..1625ffce6a 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -70,6 +70,14 @@ of lines before or after the line given by <start>. tree copy has the contents of the named file (specify `-` to make the command read from the standard input). +--date <format>:: + The value is one of the following alternatives: + {relative,local,default,iso,rfc,short}. If --date is not + provided, the value of the blame.date config variable is + used. If the blame.date config variable is also not set, the + iso format is used. For more information, See the discussion + of the --date option at linkgit:git-log[1]. + -M|<num>|:: Detect moving lines in the file as well. When a commit moves a block of lines in a file (e.g. the original file diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl deleted file mode 100644 index 6a361a2136..0000000000 --- a/Documentation/callouts.xsl +++ /dev/null @@ -1,30 +0,0 @@ -<!-- callout.xsl: converts asciidoc callouts to man page format --> -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> -<xsl:template match="co"> - <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/> -</xsl:template> -<xsl:template match="calloutlist"> - <xsl:text>.sp </xsl:text> - <xsl:apply-templates/> - <xsl:text> </xsl:text> -</xsl:template> -<xsl:template match="callout"> - <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/> - <xsl:apply-templates/> - <xsl:text>.br </xsl:text> -</xsl:template> - -<!-- sorry, this is not about callouts, but attempts to work around - spurious .sp at the tail of the line docbook stylesheets seem to add --> -<xsl:template match="simpara"> - <xsl:variable name="content"> - <xsl:apply-templates/> - </xsl:variable> - <xsl:value-of select="normalize-space($content)"/> - <xsl:if test="not(ancestor::authorblurb) and - not(ancestor::personblurb)"> - <xsl:text> </xsl:text> - </xsl:if> -</xsl:template> - -</xsl:stylesheet> diff --git a/Documentation/config.txt b/Documentation/config.txt index 6af58ff3aa..3a86d1f8f0 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2,15 +2,15 @@ CONFIGURATION FILE ------------------ The git configuration file contains a number of variables that affect -the git command's behavior. `.git/config` file for each repository -is used to store the information for that repository, and -`$HOME/.gitconfig` is used to store per user information to give -fallback values for `.git/config` file. The file `/etc/gitconfig` -can be used to store system-wide defaults. - -They can be used by both the git plumbing -and the porcelains. The variables are divided into sections, where -in the fully qualified variable name the variable itself is the last +the git command's behavior. The `.git/config` file in each repository +is used to store the configuration for that repository, and +`$HOME/.gitconfig` is used to store a per-user configuration as +fallback values for the `.git/config` file. The file `/etc/gitconfig` +can be used to store a system-wide default configuration. + +The configuration variables are used by both the git plumbing +and the porcelains. The variables are divided into sections, wherein +the fully qualified variable name of the variable itself is the last dot-separated segment and the section name is everything before the last dot. The variable names are case-insensitive and only alphanumeric characters are allowed. Some variables may appear multiple times. @@ -25,35 +25,35 @@ blank lines are ignored. The file consists of sections and variables. A section begins with the name of the section in square brackets and continues until the next section begins. Section names are not case sensitive. Only alphanumeric -characters, '`-`' and '`.`' are allowed in section names. Each variable -must belong to some section, which means that there must be section -header before first setting of a variable. +characters, `-` and `.` are allowed in section names. Each variable +must belong to some section, which means that there must be a section +header before the first setting of a variable. Sections can be further divided into subsections. To begin a subsection put its name in double quotes, separated by space from the section name, -in the section header, like in example below: +in the section header, like in the example below: -------- [section "subsection"] -------- -Subsection names can contain any characters except newline (doublequote -'`"`' and backslash have to be escaped as '`\"`' and '`\\`', -respectively) and are case sensitive. Section header cannot span multiple +Subsection names are case sensitive and can contain any characters except +newline (doublequote `"` and backslash have to be escaped as `\"` and `\\`, +respectively). Section headers cannot span multiple lines. Variables may belong directly to a section or to a given subsection. You can have `[section]` if you have `[section "subsection"]`, but you don't need to. -There is also (case insensitive) alternative `[section.subsection]` syntax. -In this syntax subsection names follow the same restrictions as for section -name. +There is also a case insensitive alternative `[section.subsection]` syntax. +In this syntax, subsection names follow the same restrictions as for section +names. All the other lines are recognized as setting variables, in the form 'name = value'. If there is no equal sign on the line, the entire line is taken as 'name' and the variable is recognized as boolean "true". The variable names are case-insensitive and only alphanumeric -characters and '`-`' are allowed. There can be more than one value +characters and `-` are allowed. There can be more than one value for a given variable; we say then that variable is multivalued. Leading and trailing whitespace in a variable value is discarded. @@ -61,26 +61,26 @@ Internal whitespace within a variable value is retained verbatim. The values following the equals sign in variable assign are all either a string, an integer, or a boolean. Boolean values may be given as yes/no, -0/1 or true/false. Case is not significant in boolean values, when +0/1, true/false or on/off. Case is not significant in boolean values, when converting value to the canonical form using '--bool' type specifier; 'git-config' will ensure that the output is "true" or "false". String values may be entirely or partially enclosed in double quotes. -You need to enclose variable value in double quotes if you want to -preserve leading or trailing whitespace, or if variable value contains -beginning of comment characters (if it contains '#' or ';'). -Double quote '`"`' and backslash '`\`' characters in variable value must -be escaped: use '`\"`' for '`"`' and '`\\`' for '`\`'. - -The following escape sequences (beside '`\"`' and '`\\`') are recognized: -'`\n`' for newline character (NL), '`\t`' for horizontal tabulation (HT, TAB) -and '`\b`' for backspace (BS). No other char escape sequence, nor octal +You need to enclose variable values in double quotes if you want to +preserve leading or trailing whitespace, or if the variable value contains +comment characters (i.e. it contains '#' or ';'). +Double quote `"` and backslash `\` characters in variable values must +be escaped: use `\"` for `"` and `\\` for `\`. + +The following escape sequences (beside `\"` and `\\`) are recognized: +`\n` for newline character (NL), `\t` for horizontal tabulation (HT, TAB) +and `\b` for backspace (BS). No other char escape sequence, nor octal char sequences are valid. -Variable value ending in a '`\`' is continued on the next line in the +Variable values ending in a `\` are continued on the next line in the customary UNIX fashion. -Some variables may require special value format. +Some variables may require a special value format. Example ~~~~~~~ @@ -221,6 +221,11 @@ core.gitProxy:: Can be overridden by the 'GIT_PROXY_COMMAND' environment variable (which always applies universally, without the special "for" handling). ++ +The special string `none` can be used as the proxy command to +specify that no proxy be used for a given domain pattern. +This is useful for excluding servers inside a firewall from +proxy use, while defaulting to a common proxy for external domains. core.ignoreStat:: If true, commands which modify both the working tree and the index @@ -384,9 +389,9 @@ core.pager:: to override git's default settings this way, you need to be explicit. For example, to disable the S option in a backward compatible manner, set `core.pager` - to "`less -+$LESS -FRX`". This will be passed to the + to `less -+$LESS -FRX`. This will be passed to the shell by git, which will translate the final command to - "`LESS=FRSX less -+FRSX -FRX`". + `LESS=FRSX less -+FRSX -FRX`. core.whitespace:: A comma separated list of common whitespace problems to @@ -424,6 +429,20 @@ relatively high IO latencies. With this set to 'true', git will do the index comparison to the filesystem data in parallel, allowing overlapping IO's. +core.createObject:: + You can set this to 'link', in which case a hardlink followed by + a delete of the source are used to make sure that object creation + will not overwrite existing objects. ++ +On some file system/operating system combinations, this is unreliable. +Set this config setting to 'rename' there; However, This will remove the +check that makes sure that existing object files will not get overwritten. + +add.ignore-errors:: + Tells 'git-add' to continue adding files when some files cannot be + added due to indexing errors. Equivalent to the '--ignore-errors' + option of linkgit:git-add[1]. + alias.*:: Command aliases for the linkgit:git[1] command wrapper - e.g. after defining "alias.last = cat-file commit HEAD", the invocation @@ -470,10 +489,14 @@ branch.autosetuprebase:: This option defaults to never. branch.<name>.remote:: - When in branch <name>, it tells 'git-fetch' which remote to fetch. - If this option is not given, 'git-fetch' defaults to remote "origin". + When in branch <name>, it tells 'git-fetch' and 'git-push' which + remote to fetch from/push to. It defaults to `origin` if no remote is + configured. `origin` is also used if you are not on any branch. branch.<name>.merge:: + Defines, together with branch.<name>.remote, the upstream branch + for the given branch. It tells 'git-fetch'/'git-pull' which + branch to merge and can also affect 'git-push' (see push.default). When in branch <name>, it tells 'git-fetch' the default refspec to be marked for merging in FETCH_HEAD. The value is handled like the remote part of a refspec, and must match a @@ -550,6 +573,25 @@ color.diff.<slot>:: whitespace errors). The values of these variables may be specified as in color.branch.<slot>. +color.grep:: + When set to `always`, always highlight matches. When `false` (or + `never`), never. When set to `true` or `auto`, use color only + when the output is written to the terminal. Defaults to `false`. + +color.grep.external:: + The string value of this variable is passed to an external 'grep' + command as a command line option if match highlighting is turned + on. If set to an empty string, no option is passed at all, + turning off coloring for external 'grep' calls; this is the default. + For GNU grep, set it to `--color=always` to highlight matches even + when a pager is used. + +color.grep.match:: + Use customized color for matches. The value of this variable + may be specified as in color.branch.<slot>. It is passed using + the environment variables 'GREP_COLOR' and 'GREP_COLORS' when + calling an external 'grep'. + color.interactive:: When set to `always`, always use colors for interactive prompts and displays (such as those used by "git-add --interactive"). @@ -567,6 +609,12 @@ color.pager:: A boolean to enable/disable colored output when the pager is in use (default is true). +color.showbranch:: + A boolean to enable/disable color in the output of + linkgit:git-show-branch[1]. May be set to `always`, + `false` (or `never`) or `auto` (or `true`), in which case colors are used + only when the output is to a terminal. Defaults to false. + color.status:: A boolean to enable/disable color in the output of linkgit:git-status[1]. May be set to `always`, @@ -641,6 +689,27 @@ diff.suppressBlankEmpty:: A boolean to inhibit the standard behavior of printing a space before each empty output line. Defaults to false. +diff.tool:: + Controls which diff tool is used. `diff.tool` overrides + `merge.tool` when used by linkgit:git-difftool[1] and has + the same valid values as `merge.tool` minus "tortoisemerge" + and plus "kompare". + +difftool.<tool>.path:: + Override the path for the given tool. This is useful in case + your tool is not in the PATH. + +difftool.<tool>.cmd:: + Specify the command to invoke the specified diff tool. + The specified command is evaluated in shell 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. + +difftool.prompt:: + Prompt before each invocation of the diff tool. + diff.wordRegex:: A POSIX Extended Regular Expression used to determine what is a "word" when performing word-by-word difference calculations. Character @@ -658,6 +727,13 @@ fetch.unpackLimit:: especially on slow filesystems. If not set, the value of `transfer.unpackLimit` is used instead. +format.attach:: + Enable multipart/mixed attachments as the default for + 'format-patch'. The value can also be a double quoted string + which will enable attachments as the default and set the + value as the boundary. See the --attach option in + linkgit:git-format-patch[1]. + format.numbered:: A boolean which can enable or disable sequence numbers in patch subjects. It defaults to "auto" which enables it only if there @@ -669,6 +745,14 @@ format.headers:: Additional email headers to include in a patch to be submitted by mail. See linkgit:git-format-patch[1]. +format.cc:: + Additional "Cc:" headers to include in a patch to be submitted + by mail. See the --cc option in linkgit:git-format-patch[1]. + +format.subjectprefix:: + The default for format-patch is to output files with the '[PATCH]' + subject prefix. Use this variable to change that prefix. + format.suffix:: The default for format-patch is to output files with the suffix `.patch`. Use this variable to change that suffix (make sure to @@ -679,6 +763,23 @@ format.pretty:: See linkgit:git-log[1], linkgit:git-show[1], linkgit:git-whatchanged[1]. +format.thread:: + The default threading style for 'git-format-patch'. Can be + either a boolean value, `shallow` or `deep`. `shallow` + threading makes every mail a reply to the head of the series, + where the head is chosen from the cover letter, the + `\--in-reply-to`, and the first patch mail, in this order. + `deep` threading makes every mail a reply to the previous one. + A true boolean value is the same as `shallow`, and a false + value disables threading. + +format.signoff:: + A boolean value which lets you enable the `-s/--signoff` option of + format-patch by default. *Note:* Adding the Signed-off-by: line to a + patch should be a conscious act and means that you certify you have + the rights to submit this work under the same open source license. + Please see the 'SubmittingPatches' document for further discussion. + gc.aggressiveWindow:: The window size parameter used in the delta compression algorithm used by 'git-gc --aggressive'. This defaults @@ -1153,7 +1254,7 @@ pager.<cmd>:: particular git subcommand when writing to a tty. If `\--paginate` or `\--no-pager` is specified on the command line, it takes precedence over this option. To disable pagination for - all commands, set `core.pager` or 'GIT_PAGER' to "`cat`". + all commands, set `core.pager` or `GIT_PAGER` to `cat`. pull.octopus:: The default merge strategy to use when pulling multiple branches @@ -1162,6 +1263,23 @@ pull.octopus:: pull.twohead:: The default merge strategy to use when pulling a single branch. +push.default:: + Defines the action git push should take if no refspec is given + on the command line, no refspec is configured in the remote, and + no refspec is implied by any of the options given on the command + line. Possible values are: ++ +* `nothing` do not push anything. +* `matching` push all matching branches. + All branches having the same name in both ends are considered to be + matching. This is the default. +* `tracking` push the current branch to its upstream branch. +* `current` push the current branch to a branch of the same name. + +rebase.stat:: + Whether to show a diffstat of what changed upstream since the last + rebase. False by default. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a diff --git a/Documentation/docbook-xsl.css b/Documentation/docbook-xsl.css index b878b385c6..e11c8f053a 100644 --- a/Documentation/docbook-xsl.css +++ b/Documentation/docbook-xsl.css @@ -16,6 +16,7 @@ body blockquote { html body { margin: 1em 5% 1em 5%; line-height: 1.2; + font-family: sans-serif; } body div { @@ -128,6 +129,15 @@ body pre { tt.literal, code.literal { color: navy; + font-family: sans-serif; +} + +code.literal:before { content: "'"; } +code.literal:after { content: "'"; } + +em { + font-style: italic; + color: #064; } div.literallayout p { @@ -137,7 +147,6 @@ div.literallayout p { div.literallayout { font-family: monospace; -# margin: 0.5em 10% 0.5em 1em; margin: 0em; color: navy; border: 1px solid silver; @@ -187,7 +196,8 @@ dt { } dt span.term { - font-style: italic; + font-style: normal; + color: navy; } div.variablelist dd p { diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index d938b42289..ab1943c712 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] 'git add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p] - [--all | [--update | -u]] [--intent-to-add | -N] + [--edit | -e] [--all | [--update | -u]] [--intent-to-add | -N] [--refresh] [--ignore-errors] [--] <filepattern>... DESCRIPTION @@ -76,6 +76,15 @@ OPTIONS bypassed and the 'patch' subcommand is invoked using each of the specified filepatterns before exiting. +-e, \--edit:: + Open the diff vs. the index in an editor and let the user + edit it. After the editor was closed, adjust the hunk headers + and apply the patch to the index. ++ +*NOTE*: Obviously, if you change anything else than the first character +on lines beginning with a space or a minus, the patch will no longer +apply. + -u:: --update:: Update only files that git already knows about, staging modified diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 9e5baa2777..735374d7df 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -3,7 +3,7 @@ git-apply(1) NAME ---- -git-apply - Apply a patch on a git index file and a working tree +git-apply - Apply a patch on a git index file and/or a working tree SYNOPSIS diff --git a/Documentation/git-archive.txt b/Documentation/git-archive.txt index ad38f7f39d..bc132c87e1 100644 --- a/Documentation/git-archive.txt +++ b/Documentation/git-archive.txt @@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] 'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>] + [--output=<file>] [--worktree-attributes] [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish> [path...] @@ -47,6 +48,12 @@ OPTIONS --prefix=<prefix>/:: Prepend <prefix>/ to each filename in the archive. +--output=<file>:: + Write the archive to <file> instead of stdout. + +--worktree-attributes:: + Look for attributes in .gitattributes in working directory too. + <extra>:: This can be any options that the archiver backend understands. See next section. diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt index e5862b9dbb..ffc02c737c 100644 --- a/Documentation/git-bisect.txt +++ b/Documentation/git-bisect.txt @@ -217,7 +217,7 @@ If you have a script that can tell if the current source code is good or bad, you can bisect by issuing the command: ------------ -$ git bisect run my_script +$ git bisect run my_script arguments ------------ Note that the script (`my_script` in the above example) should @@ -257,6 +257,13 @@ $ git bisect start HEAD v1.2 -- # HEAD is bad, v1.2 is good $ git bisect run make # "make" builds the app ------------ +* Automatically bisect a test failure between origin and HEAD: ++ +------------ +$ git bisect start HEAD origin -- # HEAD is bad, origin is good +$ git bisect run make test # "make test" builds and tests +------------ + * Automatically bisect a broken test suite: + ------------ @@ -296,6 +303,15 @@ 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. +* Automatically bisect a broken test suite: ++ +------------ +$ git bisect start HEAD HEAD~10 -- # culprit is among the last 10 +$ git bisect run sh -c "make || exit 125; ~/check_test_case.sh" +------------ ++ +Does the same as the previous example, but on a single line. + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 7f7b781f24..ae201deb7a 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -76,8 +76,8 @@ OPTIONS based sha1 expressions such as "<branchname>@\{yesterday}". -f:: - Force the creation of a new branch even if it means deleting - a branch that already exists with the same name. + Reset <branchname> to <startpoint> if <branchname> exists + already. Without `-f` 'git-branch' refuses to change an existing branch. -m:: Move/rename a branch and the corresponding reflog. @@ -100,7 +100,9 @@ OPTIONS -v:: --verbose:: - Show sha1 and commit subject line for each head. + Show sha1 and commit subject line for each head, along with + relationship to upstream branch (if any). If given twice, print + the name of the upstream branch, as well. --abbrev=<length>:: Alter the sha1's minimum display length in the output listing. @@ -109,20 +111,24 @@ OPTIONS --no-abbrev:: Display the full sha1s in the output listing rather than abbreviating them. +-t:: --track:: - 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 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 - 'git-checkout' and 'git-branch' to always behave as if '--no-track' were - given. Set it to `always` if you want this behavior when the - start-point is either a local or remote branch. + When creating a new branch, set up configuration to mark the + start-point branch as "upstream" from the new branch. This + configuration will tell git to show the relationship between the + two branches in `git status` and `git branch -v`. Furthermore, + it directs `git pull` without arguments to pull from the + upstream when the new branch is checked out. ++ +This behavior is the default when the start point is a remote branch. +Set the branch.autosetupmerge configuration variable to `false` if you +want `git checkout` and `git branch` to always behave as if '--no-track' +were given. Set it to `always` if you want this behavior when the +start-point is either a local or remote branch. --no-track:: - Ignore the branch.autosetupmerge configuration variable. + Do not set up "upstream" configuration, even if the + branch.autosetupmerge configuration variable is true. --contains <commit>:: Only list branches which contain the specified commit. diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index b191276d7a..58c8d65772 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -9,8 +9,8 @@ git-cat-file - Provide content or type and size information for repository objec SYNOPSIS -------- [verse] -'git cat-file' [-t | -s | -e | -p | <type>] <object> -'git cat-file' [--batch | --batch-check] < <list-of-objects> +'git cat-file' (-t | -s | -e | -p | <type>) <object> +'git cat-file' (--batch | --batch-check) < <list-of-objects> DESCRIPTION ----------- diff --git a/Documentation/git-check-ref-format.txt b/Documentation/git-check-ref-format.txt index 171b68377d..0b7982ea76 100644 --- a/Documentation/git-check-ref-format.txt +++ b/Documentation/git-check-ref-format.txt @@ -7,7 +7,9 @@ git-check-ref-format - Ensures that a reference name is well formed SYNOPSIS -------- +[verse] 'git check-ref-format' <refname> +'git check-ref-format' [--branch] <branchname-shorthand> DESCRIPTION ----------- @@ -23,6 +25,10 @@ imposes the following rules on how references are named: grouping, but no slash-separated component can begin with a dot `.`. +. They must contain at least one `/`. This enforces the presence of a + category like `heads/`, `tags/` etc. but the actual names are not + restricted. + . They cannot have two consecutive dots `..` anywhere. . They cannot have ASCII control characters (i.e. bytes whose @@ -30,7 +36,13 @@ imposes the following rules on how references are named: caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`, or open bracket `[` anywhere. -. They cannot end with a slash `/`. +. They cannot end with a slash `/` nor a dot `.`. + +. They cannot end with the sequence `.lock`. + +. They cannot contain a sequence `@{`. + +- They cannot contain a `\\`. 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 @@ -49,6 +61,18 @@ reference name expressions (see linkgit:git-rev-parse[1]): 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". +. at-open-brace `@{` is used as a notation to access a reflog entry. + +With the `--branch` option, it expands a branch name shorthand and +prints the name of the branch the shorthand refers to. + +EXAMPLE +------- + +git check-ref-format --branch @{-1}:: + +Print the name of the previous branch. + GIT --- diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt index 132fc4faa5..ad4b31e892 100644 --- a/Documentation/git-checkout.txt +++ b/Documentation/git-checkout.txt @@ -8,28 +8,28 @@ git-checkout - Checkout a branch or paths to the working tree SYNOPSIS -------- [verse] -'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>] +'git checkout' [-q] [-f] [-m] [<branch>] +'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>] 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... DESCRIPTION ----------- When <paths> are not given, this command switches branches by -updating the index and working tree to reflect the specified -branch, <branch>, and updating HEAD to be <branch> or, if -specified, <new_branch>. Using -b will cause <new_branch> to -be created; in this case you can use the --track or --no-track -options, which will be passed to `git branch`. +updating the index, working tree, and HEAD to reflect the specified +branch. -As a convenience, --track will default to create a branch whose -name is constructed from the specified branch name by stripping -the first namespace level. +If `-b` is given, a new branch is created and checked out, as if +linkgit:git-branch[1] were called; in this case you can +use the --track or --no-track options, which will be passed to `git +branch`. As a convenience, --track without `-b` implies branch +creation; see the description of --track below. When <paths> are given, this command does *not* switch branches. It updates the named paths in the working tree from the index file, or from a named <tree-ish> (most often a commit). In -this case, the `-b` options is meaningless and giving -either of them results in an error. <tree-ish> argument can be +this case, the `-b` and `--track` options are meaningless and giving +either of them results in an error. The <tree-ish> argument can be used to specify a specific tree-ish (i.e. commit, tag or tree) to update the index for the given paths before updating the working tree. @@ -62,27 +62,16 @@ entries; instead, unmerged entries are ignored. -b:: Create a new branch named <new_branch> and start it at - <branch>. The new branch name must pass all checks defined - by linkgit:git-check-ref-format[1]. Some of these checks - may restrict the characters allowed in a branch name. + <start_point>; see linkgit:git-branch[1] for details. -t:: --track:: - When creating a new branch, set up 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 - <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 - 'git-checkout' and 'git-branch' to always behave as if '--no-track' were - given. Set it to `always` if you want this behavior when the - start-point is either a local or remote branch. + When creating a new branch, set up "upstream" configuration. See + "--track" in linkgit:git-branch[1] for details. + -If no '-b' option was given, the name of the new branch will be -derived from the remote branch, by attempting to guess the name -of the branch on remote system. If "remotes/" or "refs/remotes/" -are prefixed, it is stripped away, and then the part up to the +If no '-b' option is given, the name of the new branch will be +derived from the remote branch. If "remotes/" or "refs/remotes/" +is prefixed it is stripped away, and then the part up to the next slash (which would be the nickname of the remote) is removed. This would tell us to use "hack" as the local branch when branching off of "origin/hack" (or "remotes/origin/hack", or even @@ -91,12 +80,12 @@ guessing results in an empty name, the guessing is aborted. You can explicitly give a name with '-b' in such a case. --no-track:: - Ignore the branch.autosetupmerge configuration variable. + Do not set up "upstream" configuration, even if the + branch.autosetupmerge configuration variable is true. -l:: - Create the new branch's reflog. This activates recording of - all changes made to the branch ref, enabling use of date - based sha1 expressions such as "<branchname>@\{yesterday}". + Create the new branch's reflog; see linkgit:git-branch[1] for + details. -m:: --merge:: @@ -124,23 +113,28 @@ the conflicted merge in the specified paths. "merge" (default) and "diff3" (in addition to what is shown by "merge" style, shows the original contents). +<branch>:: + Branch to checkout; if it refers to a branch (i.e., a name that, + when prepended with "refs/heads/", is a valid ref), then that + branch is checked out. Otherwise, if it refers to a valid + commit, your HEAD becomes "detached" and you are no longer on + any branch (see below for details). ++ +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\}"`. + <new_branch>:: Name for the new branch. +<start_point>:: + The name of a commit at which to start the new branch; see + linkgit:git-branch[1] for details. Defaults to HEAD. + <tree-ish>:: Tree to checkout from (when paths are given). If not specified, the index will be used. -<branch>:: - Branch to checkout (when no paths are given); may be any object - ID that resolves to a commit. Defaults to HEAD. -+ -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 @@ -156,12 +150,12 @@ $ git checkout v2.6.18 ------------ Earlier versions of git did not allow this and asked you to -create a temporary branch using `-b` option, but starting from +create a temporary branch using the `-b` option, but starting from version 1.5.0, the above command 'detaches' your HEAD from the -current branch and directly point at the commit named by the tag -(`v2.6.18` in the above example). +current branch and directly points at the commit named by the tag +(`v2.6.18` in the example above). -You can use usual git commands while in this state. You can use +You can use all git commands while in this state. You can use `git reset --hard $othercommit` to further move around, for example. You can make changes and create a new commit on top of a detached HEAD. You can even create a merge by using `git @@ -206,7 +200,7 @@ You should instead write: $ git checkout -- hello.c ------------ -. After working in a wrong branch, switching to the correct +. After working in the wrong branch, switching to the correct branch would be done using: + ------------ @@ -214,7 +208,7 @@ $ git checkout mytopic ------------ + However, your "wrong" branch and correct "mytopic" branch may -differ in files that you have locally modified, in which case, +differ in files that you have modified locally, in which case the above checkout would fail like this: + ------------ diff --git a/Documentation/git-clean.txt b/Documentation/git-clean.txt index 8a114509f4..be894af39f 100644 --- a/Documentation/git-clean.txt +++ b/Documentation/git-clean.txt @@ -12,14 +12,17 @@ SYNOPSIS DESCRIPTION ----------- -Removes files unknown to git. This allows to clean the working tree -from files that are not under version control. If the '-x' option is -specified, ignored files are also removed, allowing to remove all -build products. + +Cleans the working tree by recursively removing files that are not +under version control, starting from the current directory. + +Normally, only files unknown to git are removed, but if the '-x' +option is specified, ignored files are also removed. This can, for +example, be useful to remove all build products. + If any optional `<path>...` arguments are given, only those paths are affected. - OPTIONS ------- -d:: diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 4072f40d7a..b14de6c407 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -149,7 +149,7 @@ then the cloned repository will become corrupt. part of the source repository is used if no directory is explicitly given ("repo" for "/path/to/repo.git" and "foo" for "host.xz:foo/.git"). Cloning into an existing directory - is not allowed. + is only allowed if the directory is empty. :git-clone: 1 include::urls.txt[] diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 6ab2af4b61..f68b198205 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git config' [<file-option>] [type] [-z|--null] name [value [value_regex]] 'git config' [<file-option>] [type] --add name value -'git config' [<file-option>] [type] --replace-all name [value [value_regex]] +'git config' [<file-option>] [type] --replace-all name value [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get name [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex] 'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex] @@ -22,6 +22,7 @@ SYNOPSIS 'git config' [<file-option>] [-z|--null] -l | --list 'git config' [<file-option>] --get-color name [default] 'git config' [<file-option>] --get-colorbool name [stdout-is-tty] +'git config' [<file-option>] -e | --edit DESCRIPTION ----------- @@ -68,7 +69,8 @@ OPTIONS --add:: Adds a new line to the option without altering any existing - values. This is the same as providing '^$' as the value_regex. + values. This is the same as providing '^$' as the value_regex + in `--replace-all`. --get:: Get the value for a given key (optionally filtered by a regex @@ -154,13 +156,18 @@ See also <<FILES>>. When the color setting for `name` is undefined, the command uses `color.ui` as fallback. ---get-color name default:: +--get-color name [default]:: Find the color configured for `name` (e.g. `color.diff.new`) and output it as the ANSI color escape sequence to the standard output. The optional `default` parameter is used instead, if there is no color configured for `name`. +-e:: +--edit:: + Opens an editor to modify the specified config file; either + '--system', '--global', or repository (default). + [[FILES]] FILES ----- diff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt index 2da8588f4f..abaaf273bb 100644 --- a/Documentation/git-cvsexportcommit.txt +++ b/Documentation/git-cvsexportcommit.txt @@ -63,6 +63,10 @@ OPTIONS -u:: Update affected files from CVS repository before attempting export. +-k:: + Reverse CVS keyword expansion (e.g. $Revision: 1.2.3.4$ + becomes $Revision$) in working CVS checkout before applying patch. + -w:: Specify the location of the CVS checkout to use for the export. This option does not require GIT_DIR to be set before execution if the diff --git a/Documentation/git-cvsimport.txt b/Documentation/git-cvsimport.txt index 8f9ba74c8b..614e769f4e 100644 --- a/Documentation/git-cvsimport.txt +++ b/Documentation/git-cvsimport.txt @@ -24,6 +24,9 @@ repository, or incrementally import into an existing one. Splitting the CVS log into patch sets is done by 'cvsps'. At least version 2.1 is required. +*WARNING:* for certain situations the import leads to incorrect results. +Please see the section <<issues,ISSUES>> for further reference. + You should *never* do any work of your own on the branches that are created by 'git-cvsimport'. By default initial import will create and populate a "master" branch from the CVS repository's main branch which you're free @@ -164,6 +167,39 @@ If '-v' is specified, the script reports what it is doing. Otherwise, success is indicated the Unix way, i.e. by simply exiting with a zero exit status. +[[issues]] +ISSUES +------ +Problems related to timestamps: + + * If timestamps of commits in the cvs repository are not stable enough + to be used for ordering commits changes may show up in the wrong + order. + * If any files were ever "cvs import"ed more than once (e.g., import of + more than one vendor release) the HEAD contains the wrong content. + * If the timestamp order of different files cross the revision order + within the commit matching time window the order of commits may be + wrong. + +Problems related to branches: + + * Branches on which no commits have been made are not imported. + * All files from the branching point are added to a branch even if + never added in cvs. + * This applies to files added to the source branch *after* a daughter + branch was created: if previously no commit was made on the daughter + branch they will erroneously be added to the daughter branch in git. + +Problems related to tags: + +* Multiple tags on the same revision are not imported. + +If you suspect that any of these issues may apply to the repository you +want to import consider using these alternative tools which proved to be +more stable in practice: + +* cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org` +* parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs` Author ------ diff --git a/contrib/difftool/git-difftool.txt b/Documentation/git-difftool.txt index 6e2610cda6..96a6c51a4b 100644 --- a/contrib/difftool/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -3,52 +3,54 @@ git-difftool(1) NAME ---- -git-difftool - compare changes using common merge tools +git-difftool - Show changes using common diff tools SYNOPSIS -------- -'git difftool' [--tool=<tool>] [--no-prompt] ['git diff' options] +'git difftool' [--tool=<tool>] [-y|--no-prompt|--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. +between revisions using common diff tools. 'git difftool' is a frontend +to 'git-diff' and accepts the same options and arguments. OPTIONS ------- +-y:: +--no-prompt:: + Do not prompt before launching a diff tool. + +--prompt:: + Prompt before each invocation of the diff tool. + This is the default behaviour; the option is provided to + override any configuration settings. + -t <tool>:: --tool=<tool>:: - Use the merge resolution program specified by <tool>. + Use the diff tool specified by <tool>. Valid merge tools are: - kdiff3, kompare, tkdiff, meld, xxdiff, emerge, - vimdiff, gvimdiff, ecmerge, and opendiff + kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, + ecmerge, diffuse, opendiff and araxis. + -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' +If a diff tool is not specified, 'git-difftool' +will use the configuration variable `diff.tool`. If the +configuration variable `diff.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 +configuration variable `difftool.<tool>.path`. For example, you can configure the absolute path to kdiff3 by setting -`mergetool.kdiff3.path`. Otherwise, 'git-difftool' assumes the +`difftool.kdiff3.path`. Otherwise, 'git-difftool' assumes the tool is available in PATH. + -Instead of running one of the known merge tool programs, +Instead of running one of the known diff tools, 'git-difftool' can be customized to run an alternative program by specifying the command line to invoke in a configuration -variable `mergetool.<tool>.cmd`. +variable `difftool.<tool>.cmd`. + When 'git-difftool' is invoked with this tool (either through the -`-t` or `--tool` option or the `merge.tool` configuration variable) +`-t` or `--tool` option or the `diff.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` @@ -56,29 +58,27 @@ 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. +See linkgit:git-diff[1] for the full list of supported options. CONFIG VARIABLES ---------------- -merge.tool:: - The default merge tool to use. -+ -See the `--tool=<tool>` option above for more details. +'git-difftool' falls back to 'git-mergetool' config variables when the +difftool equivalents have not been defined. -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). +diff.tool:: + The default diff tool to use. -mergetool.<tool>.path:: +difftool.<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. +difftool.<tool>.cmd:: + Specify the command to invoke the specified diff tool. + See the `--tool=<tool>` option above for more details. +difftool.prompt:: + Prompt before each invocation of the diff tool. SEE ALSO -------- diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt index c1193953a1..ab527b5b31 100644 --- a/Documentation/git-filter-branch.txt +++ b/Documentation/git-filter-branch.txt @@ -94,7 +94,9 @@ OPTIONS --index-filter <command>:: This is the filter for rewriting the index. It is similar to the tree filter but does not check out the tree, which makes it much - faster. For hairy cases, see linkgit:git-update-index[1]. + faster. Frequently used with `git rm \--cached + \--ignore-unmatch ...`, see EXAMPLES below. For hairy + cases, see linkgit:git-update-index[1]. --parent-filter <command>:: This is the filter for rewriting the commit's parent list. @@ -207,19 +209,18 @@ However, if the file is absent from the tree of some commit, a simple `rm filename` will fail for that tree and commit. Thus you may instead want to use `rm -f filename` as the script. -A significantly faster version: +Using `\--index-filter` with 'git-rm' yields a significantly faster +version. Like with using `rm filename`, `git rm --cached filename` +will fail if the file is absent from the tree of a commit. If you +want to "completely forget" a file, it does not matter when it entered +history, so we also add `\--ignore-unmatch`: -------------------------------------------------------------------------- -git filter-branch --index-filter 'git rm --cached filename' HEAD +git filter-branch --index-filter 'git rm --cached --ignore-unmatch 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: diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 5061d3e4e7..8dc873fd44 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -75,6 +75,8 @@ For all objects, the following names can be used: refname:: The name of the ref (the part after $GIT_DIR/). For a non-ambiguous short name of the ref append `:short`. + The option core.warnAmbiguousRefs is used to select the strict + abbreviation mode. objecttype:: The type of the object (`blob`, `tree`, `commit`, `tag`). @@ -85,6 +87,11 @@ objectsize:: objectname:: The object name (aka SHA-1). +upstream:: + The name of a local ref which can be considered ``upstream'' + from the displayed ref. Respects `:short` in the same way as + `refname` above. + In addition to the above, for commit and tag objects, the header field names (`tree`, `parent`, `object`, `type`, and `tag`) can be used to specify the value in the header field. diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 3c29655d75..6f1fc80119 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -9,9 +9,10 @@ git-format-patch - Prepare patches for e-mail submission SYNOPSIS -------- [verse] -'git format-patch' [-k] [-o <dir> | --stdout] [--thread] - [--attach[=<boundary>] | --inline[=<boundary>]] - [-s | --signoff] [<common diff options>] +'git format-patch' [-k] [(-o|--output-directory) <dir> | --stdout] + [--thread[=<style>]] + [(--attach|--inline)[=<boundary>] | --no-attach] + [-s | --signoff] [-n | --numbered | -N | --no-numbered] [--start-number <n>] [--numbered-files] [--in-reply-to=Message-Id] [--suffix=.<sfx>] @@ -19,6 +20,7 @@ SYNOPSIS [--subject-prefix=Subject-Prefix] [--cc=<email>] [--cover-letter] + [<common diff options>] [ <since> | <revision range> ] DESCRIPTION @@ -112,15 +114,27 @@ include::diff-options.txt[] which is the commit message and the patch itself in the second part, with "Content-Disposition: attachment". +--no-attach:: + Disable the creation of an attachment, overriding the + configuration setting. + --inline[=<boundary>]:: Create multipart/mixed attachment, the first part of which is the commit message and the patch itself in the second part, with "Content-Disposition: inline". ---thread:: +--thread[=<style>]:: Add In-Reply-To and References headers to make the second and subsequent mails appear as replies to the first. Also generates the Message-Id header to reference. ++ +The optional <style> argument can be either `shallow` or `deep`. +'shallow' threading makes every mail a reply to the head of the +series, where the head is chosen from the cover letter, the +`\--in-reply-to`, and the first patch mail, in this order. 'deep' +threading makes every mail a reply to the previous one. If not +specified, defaults to the 'format.thread' configuration, or `shallow` +if that is not set. --in-reply-to=Message-Id:: Make the first mail (or all the mails with --no-thread) appear as a @@ -144,6 +158,11 @@ include::diff-options.txt[] Add a "Cc:" header to the email headers. This is in addition to any configured headers, and may be used multiple times. +--add-header=<header>:: + Add an arbitrary header to the email headers. This is in addition + to any configured headers, and may be used multiple times. + For example, --add-header="Organization: git-foo" + --cover-letter:: In addition to the patches, generate a cover letter file containing the shortlog and the overall diffstat. You can @@ -152,18 +171,17 @@ include::diff-options.txt[] --suffix=.<sfx>:: Instead of using `.patch` as the suffix for generated filenames, use specified suffix. A common alternative is - `--suffix=.txt`. + `--suffix=.txt`. Leaving this empty will remove the `.patch` + suffix. + -Note that you would need to include the leading dot `.` if you -want a filename like `0001-description-of-my-change.patch`, and -the first letter does not have to be a dot. Leaving it empty would -not add any suffix. +Note that the leading character does not have to be a dot; for example, +you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`. --no-binary:: - Don't output contents of changes in binary files, just take note - that they differ. Note that this disable the patch to be properly - applied. By default the contents of changes in those files are - encoded in the patch. + Do not output contents of changes in binary files, instead + display a notice that those files changed. Patches generated + using this option cannot be applied properly, but they are + still useful for code review. --root:: Treat the revision argument as a <revision range>, even if it @@ -174,9 +192,10 @@ not add any suffix. CONFIGURATION ------------- -You can specify extra mail header lines to be added to each message -in the repository configuration, new defaults for the subject prefix -and file suffix, and number patches when outputting more than one. +You can specify extra mail header lines to be added to each message, +defaults for the subject prefix and file suffix, number patches when +outputting more than one patch, add "Cc:" headers, configure attachments, +and sign off patches with configuration variables. ------------ [format] @@ -185,6 +204,8 @@ and file suffix, and number patches when outputting more than one. suffix = .txt numbered = auto cc = <email> + attach [ = mime-boundary-string ] + signoff = true ------------ @@ -222,8 +243,8 @@ $ git format-patch -M -B origin + Additionally, it detects and handles renames and complete rewrites intelligently to produce a renaming patch. A renaming patch reduces -the amount of text output, and generally makes it easier to review it. -Note that the "patch" program does not understand renaming patches, so +the amount of text output, and generally makes it easier to review. +Note that non-git "patch" programs won't understand renaming patches, so use it only when you know the recipient uses git to apply your patch. * Extract three topmost commits from the current branch and format them diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index 553da6cbb1..fccb82deb4 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -17,6 +17,7 @@ SYNOPSIS [-l | --files-with-matches] [-L | --files-without-match] [-z | --null] [-c | --count] [--all-match] + [--color | --no-color] [-A <post-context>] [-B <pre-context>] [-C <context>] [-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...] [<tree>...] @@ -105,6 +106,13 @@ OPTIONS Instead of showing every matched line, show the number of lines that match. +--color:: + Show colored matches. + +--no-color:: + Turn off match highlighting, even when the configuration file + gives the default to color output. + -[ABC] <context>:: Show `context` trailing (`A` -- after), or leading (`B` -- before), or both (`C` -- context) lines, and place a diff --git a/Documentation/git-imap-send.txt b/Documentation/git-imap-send.txt index eed50572e0..d016dafd49 100644 --- a/Documentation/git-imap-send.txt +++ b/Documentation/git-imap-send.txt @@ -64,6 +64,13 @@ imap.sslverify:: used by the SSL/TLS connection. Default is `true`. Ignored when imap.tunnel is set. +imap.preformattedHTML:: + A boolean to enable/disable the use of html encoding when sending + a patch. An html encoded patch will be bracketed with <pre> + and have a content type of text/html. Ironically, enabling this + option causes Thunderbird to send the patch as a plain/text, + format=fixed email. Default is `false`. + Examples ~~~~~~~~ diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index f7be5846a6..c04ae739ed 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -40,8 +40,8 @@ include::merge-options.txt[] include::merge-strategies.txt[] -If you tried a merge which resulted in a complex conflicts and -would want to start over, you can recover with 'git-reset'. +If you tried a merge which resulted in complex conflicts and +want to start over, you can recover with 'git-reset'. CONFIGURATION ------------- @@ -146,7 +146,7 @@ And here is another line that is cleanly resolved or unmodified. ------------ The area where a pair of conflicting changes happened is marked with markers -"`<<<<<<<`", "`=======`", and "`>>>>>>>`". The part before the "`=======`" +`<<<<<<<`, `=======`, and `>>>>>>>`. The part before the `=======` is typically your side, and the part afterwards is typically their side. The default format does not show what the original said in the conflicting @@ -173,8 +173,8 @@ Git makes conflict resolution easy. And here is another line that is cleanly resolved or unmodified. ------------ -In addition to the "`<<<<<<<`", "`=======`", and "`>>>>>>>`" markers, it uses -another "`|||||||`" marker that is followed by the original text. You can +In addition to the `<<<<<<<`, `=======`, and `>>>>>>>` markers, it uses +another `|||||||` marker that is followed by the original text. You can tell that the original just stated a fact, and your side simply gave in to that statement and gave up, while the other side tried to have a more positive attitude. You can sometimes come up with a better resolution by diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt new file mode 100644 index 0000000000..78eb03f0ae --- /dev/null +++ b/Documentation/git-mergetool--lib.txt @@ -0,0 +1,54 @@ +git-mergetool--lib(1) +===================== + +NAME +---- +git-mergetool--lib - Common git merge tool shell scriptlets + +SYNOPSIS +-------- +'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool--lib"' + +DESCRIPTION +----------- + +This is not a command the end user would want to run. Ever. +This documentation is meant for people who are studying the +Porcelain-ish scripts and/or are writing new ones. + +The 'git-mergetool--lib' scriptlet is designed to be sourced (using +`.`) by other shell scripts to set up functions for working +with git merge tools. + +Before sourcing 'git-mergetool--lib', your script must set `TOOL_MODE` +to define the operation mode for the functions listed below. +'diff' and 'merge' are valid values. + +FUNCTIONS +--------- +get_merge_tool:: + returns a merge tool. + +get_merge_tool_cmd:: + returns the custom command for a merge tool. + +get_merge_tool_path:: + returns the custom path for a merge tool. + +run_merge_tool:: + launches a merge tool given the tool name and a true/false + flag to indicate whether a merge base is present. + '$MERGED', '$LOCAL', '$REMOTE', and '$BASE' must be defined + for use by the merge tool. + +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/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 5d3c632872..68ed6c0956 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -26,7 +26,8 @@ OPTIONS --tool=<tool>:: Use the merge resolution program specified by <tool>. Valid merge tools are: - kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, and opendiff + kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, + diffuse, tortoisemerge, opendiff and araxis. + If a merge resolution program is not specified, 'git-mergetool' will use the configuration variable `merge.tool`. If the diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt index af19f06ed7..81e3326772 100644 --- a/Documentation/git-mktree.txt +++ b/Documentation/git-mktree.txt @@ -8,12 +8,13 @@ git-mktree - Build a tree-object from ls-tree formatted text SYNOPSIS -------- -'git mktree' [-z] +'git mktree' [-z] [--missing] [--batch] DESCRIPTION ----------- -Reads standard input in non-recursive `ls-tree` output format, -and creates a tree object. The object name of the tree object +Reads standard input in non-recursive `ls-tree` output format, and creates +a tree object. The order of the tree entries is normalised by mktree so +pre-sorting the input is not required. The object name of the tree object built is written to the standard output. OPTIONS @@ -21,6 +22,18 @@ OPTIONS -z:: Read the NUL-terminated `ls-tree -z` output instead. +--missing:: + Allow missing objects. The default behaviour (without this option) + is to verify that each tree entry's sha1 identifies an existing + object. This option has no effect on the treatment of gitlink entries + (aka "submodules") which are always allowed to be missing. + +--batch:: + Allow building of more than one tree object before exiting. Each + tree is separated by as single blank line. The final new-line is + optional. Note - if the '-z' option is used, lines are terminated + with NUL. + Author ------ Written by Junio C Hamano <gitster@pobox.com> diff --git a/Documentation/git-patch-id.txt b/Documentation/git-patch-id.txt index 477785e134..253fc0fc25 100644 --- a/Documentation/git-patch-id.txt +++ b/Documentation/git-patch-id.txt @@ -20,7 +20,7 @@ IOW, you can use this thing to look for likely duplicate commits. When dealing with 'git-diff-tree' output, it takes advantage of the fact that the patch is prefixed with the object name of the -commit, and outputs two 40-byte hexadecimal string. The first +commit, and outputs two 40-byte hexadecimal strings. The first string is the patch ID, and the second string is the commit ID. This can be used to make a mapping from patch ID to commit ID. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 4e7e5a719a..fd53c49fb8 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -24,8 +24,8 @@ every time you push into it, by setting up 'hooks' there. See documentation for linkgit:git-receive-pack[1]. -OPTIONS -------- +OPTIONS[[OPTIONS]] +------------------ <repository>:: The "remote" repository that is destination of a push operation. This parameter can be either a URL @@ -187,6 +187,28 @@ reason:: Examples -------- +git push:: + Works like `git push <remote>`, where <remote> is the + current branch's remote (or `origin`, if no remote is + configured for the current branch). + +git push origin:: + Without additional configuration, works like + `git push origin :`. ++ +The default behavior of this command when no <refspec> is given can be +configured by setting the `push` option of the remote. ++ +For example, to default to pushing only the current branch to `origin` +use `git config remote.origin.push HEAD`. Any valid <refspec> (like +the ones in the examples below) can be configured as the default for +`git push origin`. + +git push origin ::: + Push "matching" branches to `origin`. See + <refspec> in the <<OPTIONS,OPTIONS>> section above for a + description of "matching" branches. + git push origin master:: Find a ref that matches `master` in the source repository (most likely, it would find `refs/heads/master`), and update diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index da3c38cd60..26f3b7b2b0 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -192,6 +192,13 @@ Alternatively, you can undo the 'git-rebase' with git rebase --abort +CONFIGURATION +------------- + +rebase.stat:: + Whether to show a diffstat of what changed upstream since the last + rebase. False by default. + OPTIONS ------- <newbase>:: @@ -224,15 +231,22 @@ OPTIONS -s <strategy>:: --strategy=<strategy>:: - Use the given merge strategy; can be supplied more than - once to specify them in the order they should be tried. + Use the given merge strategy. If there is no `-s` option, a built-in list of strategies is used instead ('git-merge-recursive' when merging a single head, 'git-merge-octopus' otherwise). This implies --merge. -v:: --verbose:: - Display a diffstat of what changed upstream since the last rebase. + Be verbose. Implies --stat. + +--stat:: + Show a diffstat of what changed upstream since the last rebase. The + diffstat is also controlled by the configuration option rebase.stat. + +-n:: +--no-stat:: + Do not show a diffstat as part of the rebase process. --no-verify:: This option bypasses the pre-rebase hook. See also linkgit:githooks[5]. @@ -243,11 +257,23 @@ OPTIONS context exist they all must match. By default no context is ever ignored. +-f:: +--force-rebase:: + Force the rebase even if the current branch is a descendant + of the commit you are rebasing onto. Normally the command will + exit with the message "Current branch is up to date" in such a + situation. + --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. +--committer-date-is-author-date:: +--ignore-date:: + These flags are passed to 'git-am' to easily change the dates + of the rebased commits (see linkgit:git-am[1]). + -i:: --interactive:: Make a list of the commits which are about to be rebased. Let the diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt index fad983e297..9e2b4eaa38 100644 --- a/Documentation/git-remote.txt +++ b/Documentation/git-remote.txt @@ -13,9 +13,10 @@ SYNOPSIS 'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url> 'git remote rename' <old> <new> 'git remote rm' <name> +'git remote set-head' <name> [-a | -d | <branch>] 'git remote show' [-n] <name> 'git remote prune' [-n | --dry-run] <name> -'git remote update' [group] +'git remote update' [-p | --prune] [group | remote]... DESCRIPTION ----------- @@ -53,8 +54,7 @@ is created. You can give more than one `-t <branch>` to track multiple branches without grabbing all branches. + With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set -up to point at remote's `<master>` branch instead of whatever -branch the `HEAD` at the remote repository actually points at. +up to point at remote's `<master>` branch. See also the set-head command. + In mirror mode, enabled with `\--mirror`, the refs will not be stored in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option @@ -76,6 +76,30 @@ the configuration file format. Remove the remote named <name>. All remote tracking branches and configuration settings for the remote are removed. +'set-head':: + +Sets or deletes the default branch (`$GIT_DIR/remotes/<name>/HEAD`) for +the named remote. Having a default branch for a remote is not required, +but allows the name of the remote to be specified in lieu of a specific +branch. For example, if the default branch for `origin` is set to +`master`, then `origin` may be specified wherever you would normally +specify `origin/master`. ++ +With `-d`, `$GIT_DIR/remotes/<name>/HEAD` is deleted. ++ +With `-a`, the remote is queried to determine its `HEAD`, then +`$GIT_DIR/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote +`HEAD` is pointed at `next`, "`git remote set-head origin -a`" will set +`$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will +only work if `refs/remotes/origin/next` already exists; if not it must be +fetched first. ++ +Use `<branch>` to set `$GIT_DIR/remotes/<name>/HEAD` explicitly. e.g., "git +remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to +`refs/remotes/origin/master`. This will only work if +`refs/remotes/origin/master` already exists; if not it must be fetched first. ++ + 'show':: Gives some information about the remote <name>. @@ -101,6 +125,8 @@ the configuration parameter remotes.default will get used; if remotes.default is not defined, all remotes which do not have the configuration parameter remote.<name>.skipDefaultUpdate set to true will be updated. (See linkgit:git-config[1]). ++ +With `--prune` option, prune all the remotes that are updated. DISCUSSION diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index aaa8852629..c9257a10c9 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -31,11 +31,14 @@ OPTIONS Instead of incrementally packing the unpacked objects, pack everything referenced into a single pack. Especially useful when packing a repository that is used - for private development and there is no need to worry - about people fetching via dumb protocols from it. Use + for private development. Use with '-d'. This will clean up the objects that `git prune` leaves behind, but `git fsck --full` shows as dangling. ++ +Note that users fetching over dumb protocols will have to fetch the +whole new pack in order to get any contained object, no matter how many +other objects in that pack they already have locally. -A:: Same as `-a`, unless '-d' is used. Then any unreachable diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt index 3ccef2f2b3..4bbdd056da 100644 --- a/Documentation/git-rev-parse.txt +++ b/Documentation/git-rev-parse.txt @@ -26,10 +26,15 @@ OPTIONS --parseopt:: Use 'git-rev-parse' in option parsing mode (see PARSEOPT section below). ---keep-dash-dash:: +--keep-dashdash:: Only meaningful in `--parseopt` mode. Tells the option parser to echo out the first `--` met instead of skipping it. +--sq-quote:: + Use 'git-rev-parse' in shell quoting mode (see SQ-QUOTE + section below). In contrast to the `--sq` option below, this + mode does only quoting. Nothing else is done to command input. + --revs-only:: Do not output flags and parameters not meant for 'git-rev-list' command. @@ -64,7 +69,8 @@ OPTIONS properly quoted for consumption by shell. Useful when you expect your parameter to contain whitespaces and newlines (e.g. when using pickaxe `-S` with - 'git-diff-\*'). + 'git-diff-\*'). In contrast to the `--sq-quote` option, + the command input is still interpreted as usual. --not:: When showing object names, prefix them with '{caret}' and @@ -84,6 +90,11 @@ OPTIONS unfortunately named tag "master"), and show them as full refnames (e.g. "refs/heads/master"). +--abbrev-ref[={strict|loose}]:: + A non-ambiguous short name of the objects name. + The option core.warnAmbiguousRefs is used to select the strict + abbreviation mode. + --all:: Show all refs found in `$GIT_DIR/refs`. @@ -299,18 +310,18 @@ previous section means the set of commits reachable from that commit, following the commit ancestry chain. To exclude commits reachable from a commit, a prefix `{caret}` -notation is used. E.g. "`{caret}r1 r2`" means commits reachable +notation is used. E.g. `{caret}r1 r2` means commits reachable from `r2` but exclude the ones reachable from `r1`. This set operation appears so often that there is a shorthand for it. When you have two commits `r1` and `r2` (named according to the syntax explained in SPECIFYING REVISIONS above), you can ask for commits that are reachable from r2 excluding those that are reachable -from r1 by "`{caret}r1 r2`" and it can be written as "`r1..r2`". +from r1 by `{caret}r1 r2` and it can be written as `r1..r2`. -A similar notation "`r1\...r2`" is called symmetric difference +A similar notation `r1\...r2` is called symmetric difference of `r1` and `r2` and is defined as -"`r1 r2 --not $(git merge-base --all r1 r2)`". +`r1 r2 --not $(git merge-base --all r1 r2)`. It is the set of commits that are reachable from either one of `r1` or `r2` but not from both. @@ -401,6 +412,33 @@ C? option C with an optional argument" eval `echo "$OPTS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?` ------------ +SQ-QUOTE +-------- + +In `--sq-quote` mode, 'git-rev-parse' echoes on the standard output a +single line suitable for `sh(1)` `eval`. This line is made by +normalizing the arguments following `--sq-quote`. Nothing other than +quoting the arguments is done. + +If you want command input to still be interpreted as usual by +'git-rev-parse' before the output is shell quoted, see the `--sq` +option. + +Example +~~~~~~~ + +------------ +$ cat >your-git-script.sh <<\EOF +#!/bin/sh +args=$(git rev-parse --sq-quote "$@") # quote user-supplied arguments +command="git frotz -n24 $args" # and use it inside a handcrafted + # command line +eval "$command" +EOF + +$ sh your-git-script.sh "a b'c" +------------ + EXAMPLES -------- diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt index fc0a4ab441..9902da43c7 100644 --- a/Documentation/git-send-email.txt +++ b/Documentation/git-send-email.txt @@ -14,6 +14,10 @@ SYNOPSIS DESCRIPTION ----------- Takes the patches given on the command line and emails them out. +Patches can be specified as files, directories (which will send all +files in the directory), or directly as a revision list. In the +last case, any format accepted by linkgit:git-format-patch[1] can +be passed to git send-email. The header of the email is configurable by command line options. If not specified on the command line, the user will be prompted with a ReadLine @@ -39,54 +43,57 @@ OPTIONS Composing ~~~~~~~~~ ---bcc:: +--annotate:: + Review and edit each patch you're about to send. See the + CONFIGURATION section for 'sendemail.multiedit'. + +--bcc=<address>:: Specify a "Bcc:" value for each email. Default is the value of 'sendemail.bcc'. + The --bcc option must be repeated for each user you want on the bcc list. ---cc:: +--cc=<address>:: Specify a starting "Cc:" value for each email. Default is the value of 'sendemail.cc'. + The --cc option must be repeated for each user you want on the cc list. ---annotate:: - Review each patch you're about to send in an editor. The setting - 'sendemail.multiedit' defines if this will spawn one editor per patch - or one for all of them at once. - --compose:: Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an introductory message for the patch series. + -When '--compose' is used, git send-email gets less interactive will use the -values of the headers you set there. If the body of the email (what you type -after the headers and a blank line) only contains blank (or GIT: prefixed) -lines, the summary won't be sent, but git-send-email will still use the -Headers values if you don't removed them. +When '--compose' is used, git send-email will use the From, Subject, and +In-Reply-To headers specified in the message. If the body of the message +(what you type after the headers and a blank line) only contains blank +(or GIT: prefixed) lines the summary won't be sent, but From, Subject, +and In-Reply-To headers will be used unless they are removed. ++ +Missing From or In-Reply-To headers will be prompted for. + -If it wasn't able to see a header in the summary it will ask you about it -interactively after quitting your editor. +See the CONFIGURATION section for 'sendemail.multiedit'. ---from:: - Specify the sender of the emails. This will default to - the value GIT_COMMITTER_IDENT, as returned by "git var -l". - The user will still be prompted to confirm this entry. +--from=<address>:: + Specify the sender of the emails. If not specified on the command line, + the value of the 'sendemail.from' configuration option is used. If + neither the command line option nor 'sendemail.from' are set, then the + user will be prompted for the value. The default for the prompt will be + the value of GIT_AUTHOR_IDENT, or GIT_COMMITTER_IDENT if that is not + set, as returned by "git var -l". ---in-reply-to:: +--in-reply-to=<identifier>:: Specify the contents of the first In-Reply-To header. Subsequent emails will refer to the previous email instead of this if --chain-reply-to is set (the default) Only necessary if --compose is also set. If --compose is not set, this will be prompted for. ---subject:: +--subject=<string>:: Specify the initial subject of the email thread. Only necessary if --compose is also set. If --compose is not set, this will be prompted for. ---to:: +--to=<address>:: Specify the primary recipient of the emails generated. Generally, this will be the upstream maintainer of the project involved. Default is the value of the 'sendemail.to' configuration value; if that is unspecified, @@ -98,7 +105,7 @@ The --to option must be repeated for each user you want on the to list. Sending ~~~~~~~ ---envelope-sender:: +--envelope-sender=<address>:: Specify the envelope sender used to send the emails. This is useful if your default address is not the address that is subscribed to a list. If you use the sendmail binary, you must have @@ -106,12 +113,12 @@ Sending the 'sendemail.envelopesender' configuration variable; if that is unspecified, choosing the envelope sender is left to your MTA. ---smtp-encryption:: +--smtp-encryption=<encryption>:: Specify the encryption to use, either 'ssl' or 'tls'. Any other value reverts to plain SMTP. Default is the value of 'sendemail.smtpencryption'. ---smtp-pass:: +--smtp-pass[=<password>]:: Password for SMTP-AUTH. The argument is optional: If no argument is specified, then the empty string is used as the password. Default is the value of 'sendemail.smtppass', @@ -123,7 +130,7 @@ or on the command line. If a username has been specified (with specified (with '--smtp-pass' or 'sendemail.smtppass'), then the user is prompted for a password while the input is masked for privacy. ---smtp-server:: +--smtp-server=<host>:: If set, specifies the outgoing SMTP server to use (e.g. `smtp.example.com` or a raw IP address). Alternatively it can specify a full pathname of a sendmail-like program instead; @@ -133,15 +140,17 @@ user is prompted for a password while the input is masked for privacy. `/usr/lib/sendmail` if such program is available, or `localhost` otherwise. ---smtp-server-port:: +--smtp-server-port=<port>:: Specifies a port different from the default port (SMTP servers typically listen to smtp port 25 and ssmtp port - 465). This can be set with 'sendemail.smtpserverport'. + 465); symbolic port names (e.g. "submission" instead of 465) + are also accepted. The port can also be set with the + 'sendemail.smtpserverport' configuration variable. --smtp-ssl:: Legacy alias for '--smtp-encryption ssl'. ---smtp-user:: +--smtp-user=<user>:: Username for SMTP-AUTH. Default is the value of 'sendemail.smtpuser'; if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'), then authentication is not attempted. @@ -150,7 +159,7 @@ user is prompted for a password while the input is masked for privacy. Automating ~~~~~~~~~~ ---cc-cmd:: +--cc-cmd=<command>:: Specify a command to execute once per patch file which should generate patch file specific "Cc:" entries. Output of this command must be single email address per line. @@ -164,7 +173,7 @@ Automating entire patch series. Default is the value of the 'sendemail.chainreplyto' configuration value; if that is unspecified, default to --chain-reply-to. ---identity:: +--identity=<identity>:: A configuration identity. When given, causes values in the 'sendemail.<identity>' subsection to take precedence over values in the 'sendemail' section. The default identity is @@ -175,7 +184,7 @@ Automating cc list. Default is the value of 'sendemail.signedoffbycc' configuration value; if that is unspecified, default to --signed-off-by-cc. ---suppress-cc:: +--suppress-cc=<category>:: Specify an additional category of recipients to suppress the auto-cc of: + @@ -205,16 +214,39 @@ specified, as well as 'body' if --no-signed-off-cc is specified. --[no-]thread:: If this is set, the In-Reply-To header will be set on each email sent. If disabled with "--no-thread", no emails will have the In-Reply-To - header set. Default is the value of the 'sendemail.thread' configuration + header set, unless specified with --in-reply-to. + Default is the value of the 'sendemail.thread' configuration value; if that is unspecified, default to --thread. Administering ~~~~~~~~~~~~~ +--confirm=<mode>:: + Confirm just before sending: ++ +-- +- 'always' will always confirm before sending +- 'never' will never confirm before sending +- 'cc' will confirm before sending when send-email has automatically + added addresses from the patch to the Cc list +- 'compose' will confirm before sending the first message when using --compose. +- 'auto' is equivalent to 'cc' + 'compose' +-- ++ +Default is the value of 'sendemail.confirm' configuration value; if that +is unspecified, default to 'auto' unless any of the suppress options +have been specified, in which case default to 'compose'. + --dry-run:: Do everything except actually send the emails. +--[no-]format-patch:: + When an argument may be understood either as a reference or as a file name, + choose to understand it as a format-patch argument ('--format-patch') + or as a file name ('--no-format-patch'). By default, when such a conflict + occurs, git send-email will fail. + --quiet:: Make git-send-email less verbose. One line per email should be all that is output. @@ -231,12 +263,6 @@ Administering Default is the value of 'sendemail.validate'; if this is not set, default to '--validate'. ---[no-]format-patch:: - When an argument may be understood either as a reference or as a file name, - choose to understand it as a format-patch argument ('--format-patch') - or as a file name ('--no-format-patch'). By default, when such a conflict - occurs, git send-email will fail. - CONFIGURATION ------------- @@ -247,7 +273,7 @@ sendemail.aliasesfile:: sendemail.aliasfiletype:: Format of the file(s) specified in sendemail.aliasesfile. Must be - one of 'mutt', 'mailrc', 'pine', or 'gnus'. + one of 'mutt', 'mailrc', 'pine', 'elm', or 'gnus'. sendemail.multiedit:: If true (default), a single editor instance will be spawned to edit @@ -255,6 +281,11 @@ sendemail.multiedit:: summary when '--compose' is used). If false, files will be edited one after the other, spawning a new editor each time. +sendemail.confirm:: + Sets the default for whether to confirm before sending. Must be + one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm' + in the previous section for the meaning of these values. + Author ------ diff --git a/Documentation/git-shell.txt b/Documentation/git-shell.txt index 3f8d973af1..0f3ad811cf 100644 --- a/Documentation/git-shell.txt +++ b/Documentation/git-shell.txt @@ -18,9 +18,9 @@ of server-side GIT commands implementing the pull/push functionality. The commands can be executed only by the '-c' option; the shell is not interactive. -Currently, only three commands are permitted to be called, 'git-receive-pack' -'git-upload-pack' with a single required argument or 'cvs server' (to invoke -'git-cvsserver'). +Currently, only four commands are permitted to be called, 'git-receive-pack' +'git-upload-pack' and 'git-upload-archive' with a single required argument, or +'cvs server' (to invoke 'git-cvsserver'). Author ------ diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt index 7e9ff3762b..89ec5364ec 100644 --- a/Documentation/git-show-branch.txt +++ b/Documentation/git-show-branch.txt @@ -8,9 +8,11 @@ git-show-branch - Show branches and their commits SYNOPSIS -------- [verse] -'git show-branch' [--all] [--remotes] [--topo-order] [--current] +'git show-branch' [--all] [--remotes] [--topo-order | --date-order] + [--current] [--color | --no-color] [--more=<n> | --list | --independent | --merge-base] - [--no-name | --sha1-name] [--topics] [<rev> | <glob>]... + [--no-name | --sha1-name] [--topics] + [<rev> | <glob>]... 'git show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>] DESCRIPTION @@ -57,6 +59,11 @@ OPTIONS appear in topological order (i.e., descendant commits are shown before their parents). +--date-order:: + This option is similar to '--topo-order' in the sense that no + parent comes before all of its children, but otherwise commits + are ordered according to their commit date. + --sparse:: By default, the output omits merges that are reachable from only one tip being shown. This option makes them @@ -107,6 +114,14 @@ OPTIONS When no explicit <ref> parameter is given, it defaults to the current branch (or `HEAD` if it is detached). +--color:: + Color the status sign (one of these: `*` `!` `+` `-`) of each commit + corresponding to the branch it's in. + +--no-color:: + Turn off colored output, even when the configuration file gives the + default to color output. + Note that --more, --list, --independent and --merge-base options are mutually exclusive. @@ -148,9 +163,10 @@ $ git show-branch master fixes mhf ------------------------------------------------ These three branches all forked from a common commit, [master], -whose commit message is "Add 'git show-branch'. "fixes" branch -adds one commit 'Introduce "reset type"'. "mhf" branch has many -other commits. The current branch is "master". +whose commit message is "Add \'git show-branch\'". The "fixes" +branch adds one commit "Introduce "reset type" flag to "git reset"". +The "mhf" branch adds many other commits. The current branch +is "master". EXAMPLE diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 051f94d26f..a42d4c85bd 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -9,8 +9,8 @@ SYNOPSIS -------- [verse] 'git stash' list [<options>] -'git stash' (show | drop | pop ) [<stash>] -'git stash' apply [--index] [<stash>] +'git stash' ( show | drop ) [<stash>] +'git stash' ( pop | apply ) [--index] [<stash>] 'git stash' branch <branchname> [<stash>] 'git stash' [save [--keep-index] [<message>]] 'git stash' clear @@ -75,19 +75,27 @@ show [<stash>]:: it will accept any format known to 'git-diff' (e.g., `git stash show -p stash@\{1}` to view the second most recent stash in patch form). -apply [--index] [<stash>]:: +pop [<stash>]:: - Restore the changes recorded in the stash on top of the current - working tree state. When no `<stash>` is given, applies the latest - one. The working directory must match the index. + Remove a single stashed state from the stash list and apply it + on top of the current working tree state, i.e., do the inverse + operation of `git stash save`. The working directory must + match the index. + -This operation can fail with conflicts; you need to resolve them -by hand in the working tree. +Applying the state can fail with conflicts; in this case, it is not +removed from the stash list. You need to resolve the conflicts by hand +and call `git stash drop` manually afterwards. + If the `--index` option is used, then tries to reinstate not only the working tree's changes, but also the index's ones. However, this can fail, when you have conflicts (which are stored in the index, where you therefore can no longer apply the changes as they were originally). ++ +When no `<stash>` is given, `stash@\{0}` is assumed. + +apply [--index] [<stash>]:: + + Like `pop`, but do not remove the state from the stash list. branch <branchname> [<stash>]:: @@ -112,12 +120,6 @@ drop [<stash>]:: Remove a single stashed state from the stash list. When no `<stash>` is given, it removes the latest one. i.e. `stash@\{0}` -pop [<stash>]:: - - Remove a single stashed state from the stash list and apply on top - of the current working tree state. When no `<stash>` is given, - `stash@\{0}` is assumed. See also `apply`. - create:: Create a stash (which is a regular commit object) and return its @@ -163,7 +165,7 @@ $ git pull file foobar not up to date, cannot merge. $ git stash $ git pull -$ git stash apply +$ git stash pop ---------------------------------------------------------------- Interrupted workflow:: @@ -192,7 +194,7 @@ You can use 'git-stash' to simplify the above, like this: $ git stash $ edit emergency fix $ git commit -a -m "Fix in a hurry" -$ git stash apply +$ git stash pop # ... continue hacking ... ---------------------------------------------------------------- diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 3b8df44673..cd8e861ce4 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -9,10 +9,12 @@ git-submodule - Initialize, update or inspect submodules SYNOPSIS -------- [verse] -'git submodule' [--quiet] add [-b branch] [--] <repository> <path> +'git submodule' [--quiet] add [-b branch] + [--reference <repository>] [--] <repository> <path> 'git submodule' [--quiet] status [--cached] [--] [<path>...] 'git submodule' [--quiet] init [--] [<path>...] -'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--] [<path>...] +'git submodule' [--quiet] update [--init] [-N|--no-fetch] [--rebase] + [--reference <repository>] [--] [<path>...] 'git submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...] 'git submodule' [--quiet] foreach <command> 'git submodule' [--quiet] sync [--] [<path>...] @@ -113,7 +115,8 @@ init:: update:: Update the registered submodules, i.e. clone missing submodules and checkout the commit specified in the index of the containing repository. - This will make the submodules HEAD be detached. + This will make the submodules HEAD be detached unless '--rebase' is + specified or the key `submodule.$name.update` is set to `rebase`. + If the submodule is not yet initialized, and you just want to use the setting as stored in .gitmodules, you can automatically initialize the @@ -177,6 +180,23 @@ OPTIONS This option is only valid for the update command. Don't fetch new objects from the remote site. +--rebase:: + This option is only valid for the update command. + Rebase the current branch onto the commit recorded in the + superproject. If this option is given, the submodule's HEAD will not + be detached. If a a merge failure prevents this process, you will have + to resolve these failures with linkgit:git-rebase[1]. + If the key `submodule.$name.update` is set to `rebase`, this option is + implicit. + +--reference <repository>:: + This option is only valid for add and update commands. These + commands sometimes need to clone a remote repository. In this case, + this option will be passed to the linkgit:git-clone[1] command. ++ +*NOTE*: Do *not* use this option unless you have read the note +for linkgit:git-clone[1]'s --reference and --shared options carefully. + <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 cda3389331..ca3fc3de1f 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -85,6 +85,10 @@ COMMANDS specified, the prefix must include a trailing slash. Setting a prefix is useful if you wish to track multiple projects that share a common repository. +--ignore-paths=<regex>;; + When passed to 'init' or 'clone' this regular expression will + be preserved as a config key. See 'fetch' for a description + of '--ignore-paths'. 'fetch':: Fetch unfetched revisions from the Subversion remote we are @@ -97,6 +101,9 @@ COMMANDS makes 'git-log' (even without --date=local) show the same times that `svn log` would in the local timezone. +--parent;; + Fetch only from the SVN parent of the current HEAD. + This doesn't interfere with interoperating with the Subversion repository you cloned from, but if you wish for your local Git repository to be able to interoperate with someone else's local Git @@ -104,17 +111,25 @@ 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 + This allows one to specify a Perl regular expression that will cause skipping of all matching paths from checkout from SVN. - Examples: + The '--ignore-paths' option should match for every 'fetch' + (including automatic fetches due to 'clone', 'dcommit', + 'rebase', etc) on a given repository. + +config key: svn-remote.<name>.ignore-paths - --ignore-paths="^doc" - skip "doc*" directory for every fetch. + If the ignore-paths config key is set and the command + line option is also given, both regular expressions + will be used. - --ignore-paths="^[^/]+/(?:branches|tags)" - skip "branches" - and "tags" of first level directories. +Examples: - Regular expression is not persistent, you should specify - it every time when fetching. + --ignore-paths="^doc" - skip "doc*" directory for every + fetch. + + --ignore-paths="^[^/]+/(?:branches|tags)" - skip + "branches" and "tags" of first level directories. 'clone':: Runs 'init' and 'fetch'. It will automatically create a @@ -383,9 +398,18 @@ after the authors-file is modified should continue operation. config key: svn.authorsfile +--authors-prog=<filename>:: + +If this option is specified, for each SVN committer name that does not +exist in the authors file, the given file is executed with the committer +name as the first argument. The program is expected to return a single +line of the form "Name <email>", which will be treated as if included in +the authors file. + -q:: --quiet:: - Make 'git-svn' less verbose. + Make 'git-svn' less verbose. Specify a second time to make it + even less verbose. --repack[=<n>]:: --repack-flags=<flags>:: @@ -672,14 +696,14 @@ listed below are allowed: ------------------------------------------------------------------------ [svn-remote "project-a"] url = http://server.org/svn + fetch = trunk/project-a:refs/remotes/project-a/trunk branches = branches/*/project-a:refs/remotes/project-a/branches/* tags = tags/*/project-a:refs/remotes/project-a/tags/* - trunk = trunk/project-a:refs/remotes/project-a/trunk ------------------------------------------------------------------------ -Keep in mind that the '*' (asterisk) wildcard of the local ref +Keep in mind that the '\*' (asterisk) wildcard of the local ref (right of the ':') *must* be the farthest right path component; -however the remote wildcard may be anywhere as long as it's own +however the remote wildcard may be anywhere as long as it's an independent path component (surrounded by '/' or EOL). This type of configuration is not automatically created by 'init' and should be manually entered with a text-editor or using 'git-config'. diff --git a/Documentation/git-update-server-info.txt b/Documentation/git-update-server-info.txt index 35d27b0c7f..035cc3018f 100644 --- a/Documentation/git-update-server-info.txt +++ b/Documentation/git-update-server-info.txt @@ -39,12 +39,6 @@ what they are for: * info/refs -BUGS ----- -When you remove an existing ref, the command fails to update -info/refs file unless `--force` flag is given. - - Author ------ Written by Junio C Hamano <gitster@pobox.com> diff --git a/Documentation/git.txt b/Documentation/git.txt index 9a26bde73e..56d47709ac 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -9,7 +9,7 @@ git - the stupid content tracker SYNOPSIS -------- [verse] -'git' [--version] [--exec-path[=GIT_EXEC_PATH]] +'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS] @@ -43,9 +43,19 @@ 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.2/git.html[documentation for release 1.6.2] +* link:v1.6.3.2/git.html[documentation for release 1.6.3.2] * release notes for + link:RelNotes-1.6.3.2.txt[1.6.3.2], + link:RelNotes-1.6.3.1.txt[1.6.3.1], + link:RelNotes-1.6.3.txt[1.6.3]. + +* release notes for + link:RelNotes-1.6.2.5.txt[1.6.2.5], + link:RelNotes-1.6.2.4.txt[1.6.2.4], + link:RelNotes-1.6.2.3.txt[1.6.2.3], + link:RelNotes-1.6.2.2.txt[1.6.2.2], + link:RelNotes-1.6.2.1.txt[1.6.2.1], link:RelNotes-1.6.2.txt[1.6.2]. * link:v1.6.1.3/git.html[documentation for release 1.6.1.3] @@ -177,6 +187,10 @@ help ...`. environment variable. If no path is given, 'git' will print the current setting and then exit. +--html-path:: + Print the path to wherever your git HTML documentation is installed + and exit. + -p:: --paginate:: Pipe all output into 'less' (or if set, $PAGER). @@ -218,6 +232,8 @@ The link:user-manual.html#git-concepts[git concepts chapter of the user-manual] and linkgit:gitcore-tutorial[7] both provide introductions to the underlying git architecture. +See linkgit:gitworkflows[7] for an overview of recommended workflows. + See also the link:howto-index.html[howto] documents for some useful examples. @@ -635,7 +651,8 @@ SEE ALSO linkgit:gittutorial[7], linkgit:gittutorial-2[7], link:everyday.html[Everyday Git], linkgit:gitcvs-migration[7], linkgit:gitglossary[7], linkgit:gitcore-tutorial[7], -linkgit:gitcli[7], link:user-manual.html[The Git User's Manual] +linkgit:gitcli[7], link:user-manual.html[The Git User's Manual], +linkgit:gitworkflows[7] GIT --- diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 29e5929db2..be39ed7c15 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -46,20 +46,20 @@ Here are the rules regarding the "flags" that you should follow when you are scripting git: * it's preferred to use the non dashed form of git commands, which means that - you should prefer `"git foo"` to `"git-foo"`. + you should prefer `git foo` to `git-foo`. - * splitting short options to separate words (prefer `"git foo -a -b"` - to `"git foo -ab"`, the latter may not even work). + * splitting short options to separate words (prefer `git foo -a -b` + to `git foo -ab`, the latter may not even work). * when a command line option takes an argument, use the 'sticked' form. In - other words, write `"git foo -oArg"` instead of `"git foo -o Arg"` for short - options, and `"git foo --long-opt=Arg"` instead of `"git foo --long-opt Arg"` + other words, write `git foo -oArg` instead of `git foo -o Arg` for short + options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg` for long options. An option that takes optional option-argument must be written in the 'sticked' form. * when you give a revision parameter to a command, make sure the parameter is not ambiguous with a name of a file in the work tree. E.g. do not write - `"git log -1 HEAD"` but write `"git log -1 HEAD --"`; the former will not work + `git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work if you happen to have a file called `HEAD` in the work tree. @@ -99,17 +99,17 @@ usage: git-describe [options] <committish>* Negating options ~~~~~~~~~~~~~~~~ -Options with long option names can be negated by prefixing `"--no-"`. For -example, `"git branch"` has the option `"--track"` which is 'on' by default. You -can use `"--no-track"` to override that behaviour. The same goes for `"--color"` -and `"--no-color"`. +Options with long option names can be negated by prefixing `--no-`. For +example, `git branch` has the option `--track` which is 'on' by default. You +can use `--no-track` to override that behaviour. The same goes for `--color` +and `--no-color`. Aggregating short options ~~~~~~~~~~~~~~~~~~~~~~~~~ Commands that support the enhanced option parser allow you to aggregate short -options. This means that you can for example use `"git rm -rf"` or -`"git clean -fdx"`. +options. This means that you can for example use `git rm -rf` or +`git clean -fdx`. Separating argument from the option diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 1fd512bca2..1c736738cc 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -151,6 +151,10 @@ indicating whether the checkout was a branch checkout (changing branches, flag=1) or a file checkout (retrieving a file from the index, flag=0). This hook cannot affect the outcome of 'git-checkout'. +It is also run after 'git-clone', unless the --no-checkout (-n) option is +used. The first parameter given to the hook is the null-ref, the second the +ref of the new HEAD and the flag is always 1. + This hook can be used to perform repository validity checks, auto-display differences from the previous HEAD if different, or set working dir metadata properties. diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt index d1a17e2625..1b67f0a9f1 100644 --- a/Documentation/gitmodules.txt +++ b/Documentation/gitmodules.txt @@ -30,6 +30,15 @@ submodule.<name>.path:: submodule.<name>.url:: Defines an url from where the submodule repository can be cloned. +submodule.<name>.update:: + Defines what to do when the submodule is updated by the superproject. + If 'checkout' (the default), the new commit specified in the + superproject will be checked out in the submodule on a detached HEAD. + If 'rebase', the current branch of the submodule will be rebased onto + the commit specified in the superproject. + This config option is overridden if 'git submodule update' is given + the '--rebase' option. + EXAMPLES -------- diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt index c5d5596d89..c7fa949c28 100644 --- a/Documentation/gittutorial.txt +++ b/Documentation/gittutorial.txt @@ -650,6 +650,9 @@ digressions that may be interesting at this point are: smart enough to perform a close-to-optimal search even in the case of complex non-linear history with lots of merged branches. + * linkgit:gitworkflows[7]: Gives an overview of recommended + workflows. + * link:everyday.html[Everyday GIT with 20 Commands Or So] * linkgit:gitcvs-migration[7]: Git for CVS users. @@ -661,6 +664,7 @@ linkgit:gitcvs-migration[7], linkgit:gitcore-tutorial[7], linkgit:gitglossary[7], linkgit:git-help[1], +linkgit:gitworkflows[7], link:everyday.html[Everyday git], link:user-manual.html[The Git User's Manual] diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index 9afca755ed..572374f7a6 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -262,7 +262,7 @@ This commit is referred to as a "merge commit", or sometimes just a 'origin' is used for that purpose. New upstream updates will be fetched into remote <<def_tracking_branch,tracking branches>> named origin/name-of-upstream-branch, which you can see using - "`git branch -r`". + `git branch -r`. [[def_pack]]pack:: A set of objects which have been compressed into one file (to save space @@ -449,6 +449,12 @@ This commit is referred to as a "merge commit", or sometimes just a An <<def_object,object>> which is not <<def_reachable,reachable>> from a <<def_branch,branch>>, <<def_tag,tag>>, or any other reference. +[[def_upstream_branch]]upstream branch:: + The default <<def_branch,branch>> that is merged into the branch in + question (or the branch in question is rebased onto). It is configured + via branch.<name>.remote and branch.<name>.merge. If the upstream branch + of 'A' is 'origin/B' sometimes we say "'A' is tracking 'origin/B'". + [[def_working_tree]]working tree:: The tree of actual checked out files. The working tree is normally equal to the <<def_HEAD,HEAD>> plus any local changes diff --git a/Documentation/manpage-1.72.xsl b/Documentation/manpage-1.72.xsl index 4065a3a27a..b4d315cb8c 100644 --- a/Documentation/manpage-1.72.xsl +++ b/Documentation/manpage-1.72.xsl @@ -1,21 +1,14 @@ -<!-- Based on callouts.xsl. Fixes man page callouts for DocBook 1.72 XSL --> -<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> +<!-- manpage-1.72.xsl: + special settings for manpages rendered from asciidoc+docbook + handles peculiarities in docbook-xsl 1.72.0 --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> -<xsl:param name="man.output.quietly" select="1"/> -<xsl:param name="refentry.meta.get.quietly" select="1"/> +<xsl:import href="manpage-base.xsl"/> -<xsl:template match="co"> - <xsl:value-of select="concat('▓fB(',substring-after(@id,'-'),')▓fR')"/> -</xsl:template> -<xsl:template match="calloutlist"> - <xsl:text>⌂sp </xsl:text> - <xsl:apply-templates/> - <xsl:text> </xsl:text> -</xsl:template> -<xsl:template match="callout"> - <xsl:value-of select="concat('▓fB',substring-after(@arearefs,'-'),'. ▓fR')"/> - <xsl:apply-templates/> - <xsl:text>⌂br </xsl:text> -</xsl:template> +<!-- these are the special values for the roff control characters + needed for docbook-xsl 1.72.0 --> +<xsl:param name="git.docbook.backslash">▓</xsl:param> +<xsl:param name="git.docbook.dot" >⌂</xsl:param> </xsl:stylesheet> diff --git a/Documentation/manpage-base.xsl b/Documentation/manpage-base.xsl new file mode 100644 index 0000000000..a264fa6160 --- /dev/null +++ b/Documentation/manpage-base.xsl @@ -0,0 +1,35 @@ +<!-- manpage-base.xsl: + special formatting for manpages rendered from asciidoc+docbook --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- these params silence some output from xmlto --> +<xsl:param name="man.output.quietly" select="1"/> +<xsl:param name="refentry.meta.get.quietly" select="1"/> + +<!-- convert asciidoc callouts to man page format; + git.docbook.backslash and git.docbook.dot params + must be supplied by another XSL file or other means --> +<xsl:template match="co"> + <xsl:value-of select="concat( + $git.docbook.backslash,'fB(', + substring-after(@id,'-'),')', + $git.docbook.backslash,'fR')"/> +</xsl:template> +<xsl:template match="calloutlist"> + <xsl:value-of select="$git.docbook.dot"/> + <xsl:text>sp </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> +<xsl:template match="callout"> + <xsl:value-of select="concat( + $git.docbook.backslash,'fB', + substring-after(@arearefs,'-'), + '. ',$git.docbook.backslash,'fR')"/> + <xsl:apply-templates/> + <xsl:value-of select="$git.docbook.dot"/> + <xsl:text>br </xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/manpage-bold-literal.xsl b/Documentation/manpage-bold-literal.xsl new file mode 100644 index 0000000000..608eb5df62 --- /dev/null +++ b/Documentation/manpage-bold-literal.xsl @@ -0,0 +1,17 @@ +<!-- manpage-bold-literal.xsl: + special formatting for manpages rendered from asciidoc+docbook --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- render literal text as bold (instead of plain or monospace); + this makes literal text easier to distinguish in manpages + viewed on a tty --> +<xsl:template match="literal"> + <xsl:value-of select="$git.docbook.backslash"/> + <xsl:text>fB</xsl:text> + <xsl:apply-templates/> + <xsl:value-of select="$git.docbook.backslash"/> + <xsl:text>fR</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl new file mode 100644 index 0000000000..a48f5b11f3 --- /dev/null +++ b/Documentation/manpage-normal.xsl @@ -0,0 +1,13 @@ +<!-- manpage-normal.xsl: + special settings for manpages rendered from asciidoc+docbook + handles anything we want to keep away from docbook-xsl 1.72.0 --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<xsl:import href="manpage-base.xsl"/> + +<!-- these are the normal values for the roff control characters --> +<xsl:param name="git.docbook.backslash">\</xsl:param> +<xsl:param name="git.docbook.dot" >.</xsl:param> + +</xsl:stylesheet> diff --git a/Documentation/manpage-suppress-sp.xsl b/Documentation/manpage-suppress-sp.xsl new file mode 100644 index 0000000000..a63c7632a8 --- /dev/null +++ b/Documentation/manpage-suppress-sp.xsl @@ -0,0 +1,21 @@ +<!-- manpage-suppress-sp.xsl: + special settings for manpages rendered from asciidoc+docbook + handles erroneous, inline .sp in manpage output of some + versions of docbook-xsl --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + +<!-- attempt to work around spurious .sp at the tail of the line + that some versions of docbook stylesheets seem to add --> +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +</xsl:stylesheet> diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt index 1ff08ff2cc..c0f96e7070 100644 --- a/Documentation/merge-config.txt +++ b/Documentation/merge-config.txt @@ -22,7 +22,8 @@ merge.stat:: merge.tool:: Controls which merge resolution program is used by linkgit:git-mergetool[1]. Valid built-in values are: "kdiff3", - "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and + "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", + "diffuse", "ecmerge", "tortoisemerge", "araxis", and "opendiff". Any other value is treated is custom merge tool and there must be a corresponding mergetool.<tool>.cmd option. diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 637b53f898..adadf8e4bf 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -39,7 +39,8 @@ --squash:: Produce the working tree and index state as if a real - merge happened, but do not actually make a commit or + merge happened (except for the merge information), + but do not actually make a commit or move the `HEAD`, nor record `$GIT_DIR/MERGE_HEAD` to cause the next `git commit` command to create a merge commit. This allows you to create a single commit on diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt index 1276f858ad..4365b7e842 100644 --- a/Documentation/merge-strategies.txt +++ b/Documentation/merge-strategies.txt @@ -3,15 +3,15 @@ MERGE STRATEGIES resolve:: This can only resolve two heads (i.e. the current branch - and another branch you pulled from) using 3-way merge + and another branch you pulled from) using a 3-way merge algorithm. It tries to carefully detect criss-cross merge ambiguities and is considered generally safe and fast. recursive:: - This can only resolve two heads using 3-way merge - algorithm. When there are more than one common - ancestors that can be used for 3-way merge, it creates a + This can only resolve two heads using a 3-way merge + algorithm. When there is more than one common + ancestor that can be used for 3-way merge, it creates a merged tree of the common ancestors and uses that as the reference tree for the 3-way merge. This has been reported to result in fewer merge conflicts without @@ -22,11 +22,11 @@ recursive:: pulling or merging one branch. octopus:: - This resolves more than two-head case, but refuses to do - complex merge that needs manual resolution. It is + This resolves cases with more than two heads, but refuses to do + a complex merge that needs manual resolution. It is primarily meant to be used for bundling topic branch heads together. This is the default merge strategy when - pulling or merging more than one branches. + pulling or merging more than one branch. ours:: This resolves any number of heads, but the result of the diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 159390c35a..2a845b1e57 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -121,6 +121,7 @@ The placeholders are: - '%d': ref names, like the --decorate option of linkgit:git-log[1] - '%e': encoding - '%s': subject +- '%f': sanitized subject line, suitable for a filename - '%b': body - '%Cred': switch color to red - '%Cgreen': switch color to green @@ -152,3 +153,12 @@ $ git log -2 --pretty=tformat:%h 4da45bef \ 4da45be 7134973 --------------------- ++ +In addition, any unrecognized string that has a `%` in it is interpreted +as if it has `tformat:` in front of it. For example, these two are +equivalent: ++ +--------------------- +$ git log -2 --pretty=tformat:%h 4da45bef +$ git log -2 --pretty=%h 4da45bef +--------------------- diff --git a/Documentation/pretty-options.txt b/Documentation/pretty-options.txt index 5f21efe407..bff94991b6 100644 --- a/Documentation/pretty-options.txt +++ b/Documentation/pretty-options.txt @@ -1,4 +1,5 @@ --pretty[='<format>']:: +--format[='<format>']:: Pretty-print the contents of the commit logs in a given format, where '<format>' can be one of 'oneline', 'short', 'medium', @@ -17,6 +18,10 @@ configuration (see linkgit:git-config[1]). This should make "--pretty=oneline" a whole lot more readable for people using 80-column terminals. +--oneline:: + This is a shorthand for "--pretty=oneline --abbrev-commit" + used together. + --encoding[=<encoding>]:: The commit objects record the encoding used for the log message in their encoding header; this option can be used to tell the diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 7dd237c2f6..11eec941df 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -140,38 +140,38 @@ limiting may be applied. -- -n 'number':: ---max-count='number':: +--max-count=<number>:: Limit the number of commits output. ---skip='number':: +--skip=<number>:: Skip 'number' commits before starting to show the commit output. ---since='date':: ---after='date':: +--since=<date>:: +--after=<date>:: Show commits more recent than a specific date. ---until='date':: ---before='date':: +--until=<date>:: +--before=<date>:: Show commits older than a specific date. ifdef::git-rev-list[] ---max-age='timestamp':: ---min-age='timestamp':: +--max-age=<timestamp>:: +--min-age=<timestamp>:: Limit the commits output to specified time range. endif::git-rev-list[] ---author='pattern':: ---committer='pattern':: +--author=<pattern>:: +--committer=<pattern>:: Limit the commits output to ones with author/committer header lines that match the specified pattern (regular expression). ---grep='pattern':: +--grep=<pattern>:: Limit the commits output to ones with log message that matches the specified pattern (regular expression). diff --git a/Documentation/technical/api-parse-options.txt b/Documentation/technical/api-parse-options.txt index 539863b1f9..50f9e9ac17 100644 --- a/Documentation/technical/api-parse-options.txt +++ b/Documentation/technical/api-parse-options.txt @@ -60,12 +60,18 @@ Steps to parse options . in `cmd_foo(int argc, const char **argv, const char *prefix)` call - argc = parse_options(argc, argv, builtin_foo_options, builtin_foo_usage, flags); + argc = parse_options(argc, argv, prefix, builtin_foo_options, builtin_foo_usage, flags); + `parse_options()` will filter out the processed options of `argv[]` and leave the non-option arguments in `argv[]`. `argc` is updated appropriately because of the assignment. + +You can also pass NULL instead of a usage array as the fifth parameter of +parse_options(), to avoid displaying a help screen with usage info and +option list. This should only be done if necessary, e.g. to implement +a limited parser for only a subset of the options that needs to be run +before the full parser, which in turn shows the full help message. ++ Flags are the bitwise-or of: `PARSE_OPT_KEEP_DASHDASH`:: @@ -77,6 +83,28 @@ Flags are the bitwise-or of: Using this flag, processing is stopped at the first non-option argument. +`PARSE_OPT_KEEP_ARGV0`:: + Keep the first argument, which contains the program name. It's + removed from argv[] by default. + +`PARSE_OPT_KEEP_UNKNOWN`:: + Keep unknown arguments instead of erroring out. This doesn't + work for all combinations of arguments as users might expect + it to do. E.g. if the first argument in `--unknown --known` + takes a value (which we can't know), the second one is + mistakenly interpreted as a known option. Similarly, if + `PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in + `--unknown value` will be mistakenly interpreted as a + non-option, not as a value belonging to the unknown option, + the parser early. That's why parse_options() errors out if + both options are set. + +`PARSE_OPT_NO_INTERNAL_HELP`:: + By default, parse_options() handles `-h`, `--help` and + `--help-all` internally, by showing a help screen. This option + turns it off and allows one to add custom handlers for these + options, or to just leave them unknown. + Data Structure -------------- @@ -109,6 +137,10 @@ There are some macros to easily define options: Introduce a boolean option. If used, `int_var` is bitwise-ored with `mask`. +`OPT_NEGBIT(short, long, &int_var, description, mask)`:: + Introduce a boolean option. + If used, `int_var` is bitwise-anded with the inverted `mask`. + `OPT_SET_INT(short, long, &int_var, description, integer)`:: Introduce a boolean option. If used, set `int_var` to `integer`. @@ -135,9 +167,22 @@ There are some macros to easily define options: and the result will be put into `var`. See 'Option Callbacks' below for a more elaborate description. +`OPT_FILENAME(short, long, &var, description)`:: + Introduce an option with a filename argument. + The filename will be prefixed by passing the filename along with + the prefix argument of `parse_options()` to `prefix_filename()`. + `OPT_ARGUMENT(long, description)`:: Introduce a long-option argument that will be kept in `argv[]`. +`OPT_NUMBER_CALLBACK(&var, description, func_ptr)`:: + Recognize numerical options like -123 and feed the integer as + if it was an argument to the function given by `func_ptr`. + The result will be put into `var`. There can be only one such + option definition. It cannot be negated and it takes no + arguments. Short options that happen to be digits take + precedence over it. + The last element of the array must be `OPT_END()`. @@ -170,7 +215,7 @@ The function must be defined in this form: The callback mechanism is as follows: -* Inside `funct`, the only interesting member of the structure +* Inside `func`, the only interesting member of the structure given by `opt` is the void pointer `opt->value`. `\*opt->value` will be the value that is saved into `var`, if you use `OPT_CALLBACK()`. diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt index 96af8977f6..0b88a51d0b 100644 --- a/Documentation/user-manual.txt +++ b/Documentation/user-manual.txt @@ -188,7 +188,7 @@ As you can see, a commit shows who made the latest change, what they did, and why. Every commit has a 40-hexdigit id, sometimes called the "object name" or the -"SHA1 id", shown on the first line of the "git-show" output. You can usually +"SHA-1 id", shown on the first line of the "git show" output. You can usually refer to a commit by a shorter name, such as a tag or a branch name, but this longer name can also be useful. Most importantly, it is a globally unique name for this commit: so if you tell somebody else the object name (for @@ -307,7 +307,7 @@ ref: refs/heads/master Examining an old version without creating a new branch ------------------------------------------------------ -The git-checkout command normally expects a branch head, but will also +The `git checkout` command normally expects a branch head, but will also accept an arbitrary commit; for example, you can check out the commit referenced by a tag: @@ -320,7 +320,7 @@ If you want to create a new branch from this checkout, you may do so HEAD is now at 427abfa... Linux v2.6.17 ------------------------------------------------ -The HEAD then refers to the SHA1 of the commit instead of to a branch, +The HEAD then refers to the SHA-1 of the commit instead of to a branch, and git branch shows that you are no longer on a branch: ------------------------------------------------ @@ -400,7 +400,7 @@ references with the same shorthand name, see the "SPECIFYING REVISIONS" section of linkgit:git-rev-parse[1]. [[Updating-a-repository-With-git-fetch]] -Updating a repository with git-fetch +Updating a repository with git fetch ------------------------------------ Eventually the developer cloned from will do additional work in her @@ -427,7 +427,7 @@ $ git fetch linux-nfs ------------------------------------------------- New remote-tracking branches will be stored under the shorthand name -that you gave "git-remote add", in this case linux-nfs: +that you gave "git remote add", in this case linux-nfs: ------------------------------------------------- $ git branch -r @@ -516,7 +516,7 @@ $ git bisect reset to return you to the branch you were on before. -Note that the version which git-bisect checks out for you at each +Note that the version which `git bisect` checks out for you at each point is just a suggestion, and you're free to try a different version if you think it would be a good idea. For example, occasionally you may land on a commit that broke something unrelated; @@ -592,11 +592,11 @@ In addition to HEAD, there are several other special names for commits: Merges (to be discussed later), as well as operations such as -git-reset, which change the currently checked-out commit, generally +`git reset`, which change the currently checked-out commit, generally set ORIG_HEAD to the value HEAD had before the current operation. -The git-fetch operation always stores the head of the last fetched -branch in FETCH_HEAD. For example, if you run git fetch without +The `git fetch` operation always stores the head of the last fetched +branch in FETCH_HEAD. For example, if you run `git fetch` without specifying a local branch as the target of the operation ------------------------------------------------- @@ -739,7 +739,7 @@ $ git log --pretty=oneline origin..mybranch | wc -l ------------------------------------------------- Alternatively, you may often see this sort of thing done with the -lower-level command linkgit:git-rev-list[1], which just lists the SHA1's +lower-level command linkgit:git-rev-list[1], which just lists the SHA-1's of all the given commits: ------------------------------------------------- @@ -1073,9 +1073,9 @@ $ git diff shows the difference between the working tree and the index file. -Note that "git-add" always adds just the current contents of a file +Note that "git add" always adds just the current contents of a file to the index; further changes to the same file will be ignored unless -you run git-add on the file again. +you run `git add` on the file again. When you're ready, just run @@ -1136,10 +1136,10 @@ Ignoring files A project will often generate files that you do 'not' want to track with git. This typically includes files generated by a build process or temporary backup files made by your editor. Of course, 'not' tracking files with git -is just a matter of 'not' calling "`git-add`" on them. But it quickly becomes +is just a matter of 'not' calling `git add` on them. But it quickly becomes annoying to have these untracked files lying around; e.g. they make -"`git add .`" practically useless, and they keep showing up in the output of -"`git status`". +`git add .` practically useless, and they keep showing up in the output of +`git status`. You can tell git to ignore certain files by creating a file called .gitignore in the top level of your working directory, with contents such as: @@ -1349,7 +1349,7 @@ $ git add file.txt ------------------------------------------------- the different stages of that file will be "collapsed", after which -git-diff will (by default) no longer show diffs for that file. +`git diff` will (by default) no longer show diffs for that file. [[undoing-a-merge]] Undoing a merge @@ -1446,7 +1446,7 @@ Fixing a mistake by rewriting history If the problematic commit is the most recent commit, and you have not yet made that commit public, then you may just -<<undoing-a-merge,destroy it using git-reset>>. +<<undoing-a-merge,destroy it using `git reset`>>. Alternatively, you can edit the working directory and update the index to fix your @@ -1474,7 +1474,7 @@ Checking out an old version of a file In the process of undoing a previous bad change, you may find it useful to check out an older version of a particular file using -linkgit:git-checkout[1]. We've used git-checkout before to switch +linkgit:git-checkout[1]. We've used `git checkout` before to switch branches, but it has quite different behavior if it is given a path name: the command @@ -1520,10 +1520,10 @@ $ git commit -a -m "blorpl: typofix" ------------------------------------------------ After that, you can go back to what you were working on with -`git stash apply`: +`git stash pop`: ------------------------------------------------ -$ git stash apply +$ git stash pop ------------------------------------------------ @@ -1542,7 +1542,7 @@ $ git gc ------------------------------------------------- to recompress the archive. This can be very time-consuming, so -you may prefer to run git-gc when you are not doing other work. +you may prefer to run `git gc` when you are not doing other work. [[ensuring-reliability]] @@ -1634,7 +1634,7 @@ In some situations the reflog may not be able to save you. For example, suppose you delete a branch, then realize you need the history it contained. The reflog is also deleted; however, if you have not yet pruned the repository, then you may still be able to find the lost -commits in the dangling objects that git-fsck reports. See +commits in the dangling objects that `git fsck` reports. See <<dangling-objects>> for the details. ------------------------------------------------- @@ -1676,7 +1676,7 @@ Sharing development with others =============================== [[getting-updates-With-git-pull]] -Getting updates with git-pull +Getting updates with git pull ----------------------------- After you clone a repository and make a few changes of your own, you @@ -1722,7 +1722,7 @@ repository that you pulled from. <<fast-forwards,fast forward>>; instead, your branch will just be updated to point to the latest commit from the upstream branch.) -The git-pull command can also be given "." as the "remote" repository, +The `git pull` command can also be given "." as the "remote" repository, in which case it just merges in a branch from the current repository; so the commands @@ -1795,7 +1795,7 @@ Public git repositories Another way to submit changes to a project is to tell the maintainer of that project to pull the changes from your repository using linkgit:git-pull[1]. In the section "<<getting-updates-With-git-pull, -Getting updates with git-pull>>" we described this as a way to get +Getting updates with `git pull`>>" we described this as a way to get updates from the "main" repository, but it works just as well in the other direction. @@ -1847,7 +1847,7 @@ Setting up a public repository ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Assume your personal repository is in the directory ~/proj. We -first create a new clone of the repository and tell git-daemon that it +first create a new clone of the repository and tell `git daemon` that it is meant to be public: ------------------------------------------------- @@ -1878,10 +1878,10 @@ repository>>", below. Otherwise, all you need to do is start linkgit:git-daemon[1]; it will listen on port 9418. By default, it will allow access to any directory that looks like a git directory and contains the magic file -git-daemon-export-ok. Passing some directory paths as git-daemon +git-daemon-export-ok. Passing some directory paths as `git daemon` arguments will further restrict the exports to those paths. -You can also run git-daemon as an inetd service; see the +You can also run `git daemon` as an inetd service; see the linkgit:git-daemon[1] man page for details. (See especially the examples section.) @@ -1942,7 +1942,7 @@ or just $ git push ssh://yourserver.com/~you/proj.git master ------------------------------------------------- -As with git-fetch, git-push will complain if this does not result in a +As with `git fetch`, `git push` will complain if this does not result in a <<fast-forwards,fast forward>>; see the following section for details on handling this case. @@ -1952,7 +1952,7 @@ repository that has a checked-out working tree, but the working tree will not be updated by the push. This may lead to unexpected results if the branch you push to is the currently checked-out branch! -As with git-fetch, you may also set up configuration options to +As with `git fetch`, you may also set up configuration options to save typing; so, for example, after ------------------------------------------------- @@ -1988,13 +1988,13 @@ error: failed to push to 'ssh://yourserver.com/~you/proj.git' This can happen, for example, if you: - - use `git-reset --hard` to remove already-published commits, or - - use `git-commit --amend` to replace already-published commits + - use `git reset --hard` to remove already-published commits, or + - use `git commit --amend` to replace already-published commits (as in <<fixing-a-mistake-by-rewriting-history>>), or - - use `git-rebase` to rebase any already-published commits (as + - use `git rebase` to rebase any already-published commits (as in <<using-git-rebase>>). -You may force git-push to perform the update anyway by preceding the +You may force `git push` to perform the update anyway by preceding the branch name with a plus sign: ------------------------------------------------- @@ -2036,7 +2036,7 @@ advantages over the central shared repository: - Git's ability to quickly import and merge patches allows a single maintainer to process incoming changes even at very - high rates. And when that becomes too much, git-pull provides + high rates. And when that becomes too much, `git pull` provides an easy way for that maintainer to delegate this job to other maintainers while still allowing optional review of incoming changes. @@ -2404,7 +2404,7 @@ use them, and then explain some of the problems that can arise because you are rewriting history. [[using-git-rebase]] -Keeping a patch series up to date using git-rebase +Keeping a patch series up to date using git rebase -------------------------------------------------- Suppose that you create a branch "mywork" on a remote-tracking branch @@ -2468,9 +2468,9 @@ patches to the new mywork. The result will look like: ................................................ In the process, it may discover conflicts. In that case it will stop -and allow you to fix the conflicts; after fixing conflicts, use "git-add" +and allow you to fix the conflicts; after fixing conflicts, use `git add` to update the index with those contents, and then, instead of -running git-commit, just run +running `git commit`, just run ------------------------------------------------- $ git rebase --continue @@ -2508,7 +2508,7 @@ with $ git tag bad mywork~5 ------------------------------------------------- -(Either gitk or git-log may be useful for finding the commit.) +(Either gitk or `git log` may be useful for finding the commit.) Then check out that commit, edit it, and rebase the rest of the series on top of it (note that we could check out the commit on a temporary @@ -2549,12 +2549,12 @@ $ gitk origin..mywork & and browse through the list of patches in the mywork branch using gitk, applying them (possibly in a different order) to mywork-new using -cherry-pick, and possibly modifying them as you go using `commit --amend`. +cherry-pick, and possibly modifying them as you go using `git commit --amend`. The linkgit:git-gui[1] command may also help as it allows you to individually select diff hunks for inclusion in the index (by right-clicking on the diff hunk and choosing "Stage Hunk for Commit"). -Another technique is to use git-format-patch to create a series of +Another technique is to use `git format-patch` to create a series of patches, then reset the state to before the patches: ------------------------------------------------- @@ -2662,7 +2662,7 @@ you know is that D is bad, that Z is good, and that linkgit:git-bisect[1] identifies C as the culprit, how will you figure out that the problem is due to this change in semantics? -When the result of a git-bisect is a non-merge commit, you should +When the result of a `git bisect` is a non-merge commit, you should normally be able to discover the problem by examining just that commit. Developers can make this easy by breaking their changes into small self-contained commits. That won't help in the case above, however, @@ -2725,7 +2725,7 @@ master branch. In more detail: git fetch and fast-forwards --------------------------- -In the previous example, when updating an existing branch, "git-fetch" +In the previous example, when updating an existing branch, "git fetch" checks to make sure that the most recent commit on the remote branch is a descendant of the most recent commit on your copy of the branch before updating your copy of the branch to point at the new @@ -2751,7 +2751,7 @@ resulting in a situation like: o--o--o <-- new head of the branch ................................................ -In this case, "git-fetch" will fail, and print out a warning. +In this case, "git fetch" will fail, and print out a warning. In that case, you can still force git to update to the new head, as described in the following section. However, note that in the @@ -2760,7 +2760,7 @@ unless you've already created a reference of your own pointing to them. [[forcing-fetch]] -Forcing git-fetch to do non-fast-forward updates +Forcing git fetch to do non-fast-forward updates ------------------------------------------------ If git fetch fails because the new head of a branch is not a @@ -2865,8 +2865,8 @@ The Object Database We already saw in <<understanding-commits>> that all commits are stored under a 40-digit "object name". In fact, all the information needed to represent the history of a project is stored in objects with such names. -In each case the name is calculated by taking the SHA1 hash of the -contents of the object. The SHA1 hash is a cryptographic hash function. +In each case the name is calculated by taking the SHA-1 hash of the +contents of the object. The SHA-1 hash is a cryptographic hash function. What that means to us is that it is impossible to find two different objects with the same name. This has a number of advantages; among others: @@ -2877,10 +2877,10 @@ others: same content stored in two repositories will always be stored under the same name. - Git can detect errors when it reads an object, by checking that the - object's name is still the SHA1 hash of its contents. + object's name is still the SHA-1 hash of its contents. (See <<object-details>> for the details of the object formatting and -SHA1 calculation.) +SHA-1 calculation.) There are four different types of objects: "blob", "tree", "commit", and "tag". @@ -2926,9 +2926,9 @@ committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700 As you can see, a commit is defined by: -- a tree: The SHA1 name of a tree object (as defined below), representing +- a tree: The SHA-1 name of a tree object (as defined below), representing the contents of a directory at a certain point in time. -- parent(s): The SHA1 name of some number of commits which represent the +- parent(s): The SHA-1 name of some number of commits which represent the immediately previous step(s) in the history of the project. The example above has one parent; merge commits may have more than one. A commit with no parents is called a "root" commit, and @@ -2977,13 +2977,13 @@ $ git ls-tree fb3a8bdd0ce ------------------------------------------------ As you can see, a tree object contains a list of entries, each with a -mode, object type, SHA1 name, and name, sorted by name. It represents +mode, object type, SHA-1 name, and name, sorted by name. It represents the contents of a single directory tree. The object type may be a blob, representing the contents of a file, or another tree, representing the contents of a subdirectory. Since trees -and blobs, like all other objects, are named by the SHA1 hash of their -contents, two trees have the same SHA1 name if and only if their +and blobs, like all other objects, are named by the SHA-1 hash of their +contents, two trees have the same SHA-1 name if and only if their contents (including, recursively, the contents of all subdirectories) are identical. This allows git to quickly determine the differences between two related tree objects, since it can ignore any entries with @@ -3029,15 +3029,15 @@ currently checked out. Trust ~~~~~ -If you receive the SHA1 name of a blob from one source, and its contents +If you receive the SHA-1 name of a blob from one source, and its contents from another (possibly untrusted) source, you can still trust that those -contents are correct as long as the SHA1 name agrees. This is because -the SHA1 is designed so that it is infeasible to find different contents +contents are correct as long as the SHA-1 name agrees. This is because +the SHA-1 is designed so that it is infeasible to find different contents that produce the same hash. -Similarly, you need only trust the SHA1 name of a top-level tree object +Similarly, you need only trust the SHA-1 name of a top-level tree object to trust the contents of the entire directory that it refers to, and if -you receive the SHA1 name of a commit from a trusted source, then you +you receive the SHA-1 name of a commit from a trusted source, then you can easily verify the entire history of commits reachable through parents of that commit, and all of those contents of the trees referred to by those commits. @@ -3049,7 +3049,7 @@ that you trust that commit, and the immutability of the history of commits tells others that they can trust the whole history. In other words, you can easily validate a whole archive by just -sending out a single email that tells the people the name (SHA1 hash) +sending out a single email that tells the people the name (SHA-1 hash) of the top commit, and digitally sign that email using something like GPG/PGP. @@ -3090,7 +3090,7 @@ How git stores objects efficiently: pack files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Newly created objects are initially created in a file named after the -object's SHA1 hash (stored in .git/objects). +object's SHA-1 hash (stored in .git/objects). Unfortunately this system becomes inefficient once a project has a lot of objects. Try this on an old project: @@ -3131,7 +3131,7 @@ $ git prune to remove any of the "loose" objects that are now contained in the pack. This will also remove any unreferenced objects (which may be -created when, for example, you use "git-reset" to remove a commit). +created when, for example, you use "git reset" to remove a commit). You can verify that the loose objects are gone by looking at the .git/objects directory or by running @@ -3160,7 +3160,7 @@ branch still exists, as does everything it pointed to. The branch pointer itself just doesn't, since you replaced it with another one. There are also other situations that cause dangling objects. For -example, a "dangling blob" may arise because you did a "git-add" of a +example, a "dangling blob" may arise because you did a "git add" of a file, but then, before you actually committed it and made it part of the bigger picture, you changed something else in that file and committed that *updated* thing--the old state that you added originally ends up @@ -3210,7 +3210,7 @@ Usually, dangling blobs and trees aren't very interesting. They're almost always the result of either being a half-way mergebase (the blob will often even have the conflict markers from a merge in it, if you have had conflicting merges that you fixed up by hand), or simply -because you interrupted a "git-fetch" with ^C or something like that, +because you interrupted a "git fetch" with ^C or something like that, leaving _some_ of the new objects in the object database, but just dangling and useless. @@ -3225,9 +3225,9 @@ and they'll be gone. But you should only run "git prune" on a quiescent repository--it's kind of like doing a filesystem fsck recovery: you don't want to do that while the filesystem is mounted. -(The same is true of "git-fsck" itself, btw, but since -git-fsck never actually *changes* the repository, it just reports -on what it found, git-fsck itself is never "dangerous" to run. +(The same is true of "git fsck" itself, btw, but since +`git fsck` never actually *changes* the repository, it just reports +on what it found, `git fsck` itself is never 'dangerous' to run. Running it while somebody is actually changing the repository can cause confusing and scary messages, but it won't actually do anything bad. In contrast, running "git prune" while somebody is actively changing the @@ -3297,7 +3297,7 @@ $ git hash-object -w somedirectory/myfile ------------------------------------------------ which will create and store a blob object with the contents of -somedirectory/myfile, and output the sha1 of that object. if you're +somedirectory/myfile, and output the SHA-1 of that object. if you're extremely lucky it might be 4b9458b3786228369c63936db65827de3cc06200, in which case you've guessed right, and the corruption is fixed! @@ -3359,7 +3359,7 @@ The index ----------- The index is a binary file (generally kept in .git/index) containing a -sorted list of path names, each with permissions and the SHA1 of a blob +sorted list of path names, each with permissions and the SHA-1 of a blob object; linkgit:git-ls-files[1] can show you the contents of the index: ------------------------------------------------- @@ -3489,14 +3489,14 @@ done NOTE: Do not use local URLs here if you plan to publish your superproject! -See what files `git-submodule` created: +See what files `git submodule` created: ------------------------------------------------- $ ls -a . .. .git .gitmodules a b c d ------------------------------------------------- -The `git-submodule add <repo> <path>` command does a couple of things: +The `git submodule add <repo> <path>` command does a couple of things: - It clones the submodule from <repo> to the given <path> under the current directory and by default checks out the master branch. @@ -3542,7 +3542,7 @@ init` to add the submodule repository URLs to `.git/config`: $ git submodule init ------------------------------------------------- -Now use `git-submodule update` to clone the repositories and check out the +Now use `git submodule update` to clone the repositories and check out the commits specified in the superproject: ------------------------------------------------- @@ -3552,8 +3552,8 @@ $ ls -a . .. .git a.txt ------------------------------------------------- -One major difference between `git-submodule update` and `git-submodule add` is -that `git-submodule update` checks out a specific commit, rather than the tip +One major difference between `git submodule update` and `git submodule add` is +that `git submodule update` checks out a specific commit, rather than the tip of a branch. It's like checking out a tag: the head is detached, so you're not working on a branch. @@ -3754,7 +3754,7 @@ unsaved state that you might want to restore later!) your current index. Normal operation is just ------------------------------------------------- -$ git read-tree <sha1 of tree> +$ git read-tree <SHA-1 of tree> ------------------------------------------------- and your index file will now be equivalent to the tree that you saved @@ -3769,7 +3769,7 @@ You update your working directory from the index by "checking out" files. This is not a very common operation, since normally you'd just keep your files updated, and rather than write to your working directory, you'd tell the index files about the changes in your -working directory (i.e. `git-update-index`). +working directory (i.e. `git update-index`). However, if you decide to jump to a new version, or check out somebody else's version, or just restore a previous tree, you'd populate your @@ -3782,7 +3782,7 @@ $ git checkout-index filename or, if you want to check out all of the index, use `-a`. -NOTE! git-checkout-index normally refuses to overwrite old files, so +NOTE! `git checkout-index` normally refuses to overwrite old files, so if you have an old version of the tree already checked out, you will need to use the "-f" flag ('before' the "-a" flag or the filename) to 'force' the checkout. @@ -3820,7 +3820,7 @@ $ git commit-tree <tree> -p <parent> [-p <parent2> ..] and then giving the reason for the commit on stdin (either through redirection from a pipe or file, or by just typing it at the tty). -git-commit-tree will return the name of the object that represents +`git commit-tree` will return the name of the object that represents that commit, and you should save it away for later use. Normally, you'd commit a new `HEAD` state, and while git doesn't care where you save the note about that state, in practice we tend to just write the @@ -3889,7 +3889,7 @@ $ git cat-file blob|tree|commit|tag <objectname> to show its contents. NOTE! Trees have binary content, and as a result there is a special helper for showing that content, called -`git-ls-tree`, which turns the binary content into a more easily +`git ls-tree`, which turns the binary content into a more easily readable form. It's especially instructive to look at "commit" objects, since those @@ -3978,13 +3978,13 @@ $ git ls-files --unmerged ------------------------------------------------ Each line of the `git ls-files --unmerged` output begins with -the blob mode bits, blob SHA1, 'stage number', and the +the blob mode bits, blob SHA-1, 'stage number', and the filename. The 'stage number' is git's way to say which tree it came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD` tree, and stage3 `$target` tree. Earlier we said that trivial merges are done inside -`git-read-tree -m`. For example, if the file did not change +`git read-tree -m`. For example, if the file did not change from `$orig` to `HEAD` nor `$target`, or if the file changed from `$orig` to `HEAD` and `$orig` to `$target` the same way, obviously the final outcome is what is in `HEAD`. What the @@ -4011,20 +4011,20 @@ $ mv -f hello.c~2 hello.c $ git update-index hello.c ------------------------------------------------- -When a path is in the "unmerged" state, running `git-update-index` for +When a path is in the "unmerged" state, running `git update-index` for that path tells git to mark the path resolved. The above is the description of a git merge at the lowest level, to help you understand what conceptually happens under the hood. -In practice, nobody, not even git itself, runs `git-cat-file` three times -for this. There is a `git-merge-index` program that extracts the +In practice, nobody, not even git itself, runs `git cat-file` three times +for this. There is a `git merge-index` program that extracts the stages to temporary files and calls a "merge" script on it: ------------------------------------------------- $ git merge-index git-merge-one-file hello.c ------------------------------------------------- -and that is what higher level `git-merge -s resolve` is implemented with. +and that is what higher level `git merge -s resolve` is implemented with. [[hacking-git]] Hacking git @@ -4045,12 +4045,12 @@ objects). There are currently four different object types: "blob", Regardless of object type, all objects share the following characteristics: they are all deflated with zlib, and have a header that not only specifies their type, but also provides size information -about the data in the object. It's worth noting that the SHA1 hash +about the data in the object. It's worth noting that the SHA-1 hash that is used to name the object is the hash of the original data plus this header, so `sha1sum` 'file' does not match the object name for 'file'. (Historical note: in the dawn of the age of git the hash -was the sha1 of the 'compressed' object.) +was the SHA-1 of the 'compressed' object.) As a result, the general consistency of an object can always be tested independently of the contents or the type of the object: all objects can @@ -4061,7 +4061,7 @@ size> {plus} <byte\0> {plus} <binary object data>. The structured objects can further have their structure and connectivity to other objects verified. This is generally done with -the `git-fsck` program, which generates a full dependency graph +the `git fsck` program, which generates a full dependency graph of all objects, and verifies their internal consistency (in addition to just verifying their superficial consistency through the hash). @@ -4120,7 +4120,7 @@ functions like `get_sha1_basic()` or the likes. This is just to get you into the groove for the most libified part of Git: the revision walker. -Basically, the initial version of `git-log` was a shell script: +Basically, the initial version of `git log` was a shell script: ---------------------------------------------------------------- $ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \ @@ -4129,20 +4129,20 @@ $ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \ What does this mean? -`git-rev-list` is the original version of the revision walker, which +`git rev-list` is the original version of the revision walker, which _always_ printed a list of revisions to stdout. It is still functional, and needs to, since most new Git programs start out as scripts using -`git-rev-list`. +`git rev-list`. -`git-rev-parse` is not as important any more; it was only used to filter out +`git rev-parse` is not as important any more; it was only used to filter out options that were relevant for the different plumbing commands that were called by the script. -Most of what `git-rev-list` did is contained in `revision.c` and +Most of what `git rev-list` did is contained in `revision.c` and `revision.h`. It wraps the options in a struct named `rev_info`, which controls how and what revisions are walked, and more. -The original job of `git-rev-parse` is now taken by the function +The original job of `git rev-parse` is now taken by the function `setup_revisions()`, which parses the revisions and the common command line options for the revision walker. This information is stored in the struct `rev_info` for later consumption. You can do your own command line option @@ -4155,7 +4155,7 @@ just have a look at the first implementation of `cmd_log()`; call `git show v1.3.0{tilde}155^2{tilde}4` and scroll down to that function (note that you no longer need to call `setup_pager()` directly). -Nowadays, `git-log` is a builtin, which means that it is _contained_ in the +Nowadays, `git log` is a builtin, which means that it is _contained_ in the command `git`. The source side of a builtin is - a function called `cmd_<bla>`, typically defined in `builtin-<bla>.c`, @@ -4171,7 +4171,7 @@ since they share quite a bit of code. In that case, the commands which are _not_ named like the `.c` file in which they live have to be listed in `BUILT_INS` in the `Makefile`. -`git-log` looks more complicated in C than it does in the original script, +`git log` looks more complicated in C than it does in the original script, but that allows for a much greater flexibility and performance. Here again it is a good point to take a pause. @@ -4182,9 +4182,9 @@ the organization of Git (after you know the basic concepts). So, think about something which you are interested in, say, "how can I access a blob just knowing the object name of it?". The first step is to find a Git command with which you can do it. In this example, it is either -`git-show` or `git-cat-file`. +`git show` or `git cat-file`. -For the sake of clarity, let's stay with `git-cat-file`, because it +For the sake of clarity, let's stay with `git cat-file`, because it - is plumbing, and @@ -4198,7 +4198,7 @@ it does. ------------------------------------------------------------------ git_config(git_default_config); if (argc != 3) - usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>"); + usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>"); if (get_sha1(argv[2], sha1)) die("Not a valid object name %s", argv[2]); ------------------------------------------------------------------ @@ -4243,10 +4243,10 @@ To find out how the result can be used, just read on in `cmd_cat_file()`: ----------------------------------- Sometimes, you do not know where to look for a feature. In many such cases, -it helps to search through the output of `git log`, and then `git-show` the +it helps to search through the output of `git log`, and then `git show` the corresponding commit. -Example: If you know that there was some test case for `git-bundle`, but +Example: If you know that there was some test case for `git bundle`, but do not remember where it was (yes, you _could_ `git grep bundle t/`, but that does not illustrate the point!): @@ -4530,7 +4530,7 @@ The basic requirements: - Whenever possible, section headings should clearly describe the task they explain how to do, in language that requires no more knowledge than necessary: for example, "importing patches into a project" rather - than "the git-am command" + than "the `git am` command" Think about how to create a clear chapter dependency graph that will allow people to get to important topics without necessarily reading diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 9d65aed417..39cde784c9 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.6.2.5 +DEF_VER=v1.6.3.GIT LF=' ' @@ -3,6 +3,11 @@ all:: # Define V=1 to have a more verbose compile. # +# Define SHELL_PATH to a POSIX shell if your /bin/sh is broken. +# +# Define SANE_TOOL_PATH to a colon-separated list of paths to prepend +# to PATH if your tools in /usr/bin are broken. +# # Define SNPRINTF_RETURNS_BOGUS if your are on a system which snprintf() # or vsnprintf() return -1 instead of number of characters which would # have been written to the final string if enough space had been available. @@ -52,6 +57,10 @@ all:: # # Define NO_MKDTEMP if you don't have mkdtemp in the C library. # +# Define NO_MKSTEMPS if you don't have mkstemps in the C library. +# +# Define NO_LIBGEN_H if you don't have libgen.h. +# # Define NO_SYS_SELECT_H if you don't have sys/select.h. # # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link. @@ -91,6 +100,10 @@ all:: # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). # +# Define NEEDS_RESOLV if linking with -lnsl and/or -lsocket is not enough. +# Notably on Solaris hstrerror resides in libresolv and on Solaris 7 +# inet_ntop and inet_pton additionally reside there. +# # Define NO_MMAP if you want to avoid mmap. # # Define NO_PTHREADS if you do not have or do not want to use Pthreads. @@ -126,6 +139,12 @@ all:: # randomly break unless your underlying filesystem supports those sub-second # times (my ext3 doesn't). # +# Define USE_ST_TIMESPEC if your "struct stat" uses "st_ctimespec" instead of +# "st_ctim" +# +# Define NO_NSEC if your "struct stat" does not have "st_ctim.tv_nsec" +# available. This automatically turns USE_NSEC off. +# # Define USE_STDEV below if you want git to care about the underlying device # change being considered an inode change from the update-index perspective. # @@ -139,6 +158,8 @@ all:: # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # +# Define NO_PERL if you do not want Perl scripts or libraries at all. +# # Define NO_TCLTK if you do not want Tcl/Tk GUI. # # The TCL_PATH variable governs the location of the Tcl interpreter @@ -159,6 +180,20 @@ all:: # Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call # your external grep (e.g., if your system lacks grep, if its grep is # broken, or spawning external process is slower than built-in grep git has). +# +# Define UNRELIABLE_FSTAT if your system's fstat does not return the same +# information on a not yet closed file that lstat would return for the same +# file after it was closed. +# +# Define OBJECT_CREATION_USES_RENAMES if your operating systems has problems +# when hardlinking a file to another name and unlinking the original file right +# away (some NTFS drivers seem to zero the contents in that scenario). +# +# Define NO_CROSS_DIRECTORY_HARDLINKS if you plan to distribute the installed +# programs as a tar, where bin/ and libexec/ might be on different file systems. +# +# Define USE_NED_ALLOCATOR if you want to replace the platforms default +# memory allocators with the nedmalloc allocator written by Niall Douglas. GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE @$(SHELL_PATH) ./GIT-VERSION-GEN @@ -209,6 +244,7 @@ ETC_GITCONFIG = etc/gitconfig endif lib = lib # DESTDIR= +pathsep = : # default configuration for gitweb GITWEB_CONFIG = gitweb_config.perl @@ -257,14 +293,28 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__ BASIC_CFLAGS = BASIC_LDFLAGS = +# Guard against environment variables +BUILTIN_OBJS = +BUILT_INS = +COMPAT_CFLAGS = +COMPAT_OBJS = +LIB_H = +LIB_OBJS = +PROGRAMS = +SCRIPT_PERL = +SCRIPT_SH = +TEST_PROGRAMS = + SCRIPT_SH += git-am.sh SCRIPT_SH += git-bisect.sh +SCRIPT_SH += git-difftool--helper.sh SCRIPT_SH += git-filter-branch.sh SCRIPT_SH += git-lost-found.sh SCRIPT_SH += git-merge-octopus.sh SCRIPT_SH += git-merge-one-file.sh SCRIPT_SH += git-merge-resolve.sh SCRIPT_SH += git-mergetool.sh +SCRIPT_SH += git-mergetool--lib.sh SCRIPT_SH += git-parse-remote.sh SCRIPT_SH += git-pull.sh SCRIPT_SH += git-quiltimport.sh @@ -278,6 +328,7 @@ SCRIPT_SH += git-submodule.sh SCRIPT_SH += git-web--browse.sh SCRIPT_PERL += git-add--interactive.perl +SCRIPT_PERL += git-difftool.perl SCRIPT_PERL += git-archimport.perl SCRIPT_PERL += git-cvsexportcommit.perl SCRIPT_PERL += git-cvsimport.perl @@ -301,7 +352,6 @@ PROGRAMS += git-index-pack$X PROGRAMS += git-merge-index$X PROGRAMS += git-merge-tree$X PROGRAMS += git-mktag$X -PROGRAMS += git-mktree$X PROGRAMS += git-pack-redundant$X PROGRAMS += git-patch-id$X PROGRAMS += git-shell$X @@ -333,7 +383,7 @@ BUILT_INS += git-whatchanged$X ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) # what 'all' will build but not install in gitexecdir -OTHER_PROGRAMS = git$X gitweb/gitweb.cgi +OTHER_PROGRAMS = git$X # Set paths to tools early so that they can be used for version tests. ifndef SHELL_PATH @@ -412,6 +462,7 @@ LIB_OBJS += archive-tar.o LIB_OBJS += archive-zip.o LIB_OBJS += attr.o LIB_OBJS += base85.o +LIB_OBJS += bisect.o LIB_OBJS += blob.o LIB_OBJS += branch.o LIB_OBJS += bundle.o @@ -512,6 +563,7 @@ BUILTIN_OBJS += builtin-add.o BUILTIN_OBJS += builtin-annotate.o BUILTIN_OBJS += builtin-apply.o BUILTIN_OBJS += builtin-archive.o +BUILTIN_OBJS += builtin-bisect--helper.o BUILTIN_OBJS += builtin-blame.o BUILTIN_OBJS += builtin-branch.o BUILTIN_OBJS += builtin-bundle.o @@ -553,6 +605,7 @@ BUILTIN_OBJS += builtin-merge-base.o BUILTIN_OBJS += builtin-merge-file.o BUILTIN_OBJS += builtin-merge-ours.o BUILTIN_OBJS += builtin-merge-recursive.o +BUILTIN_OBJS += builtin-mktree.o BUILTIN_OBJS += builtin-mv.o BUILTIN_OBJS += builtin-name-rev.o BUILTIN_OBJS += builtin-pack-objects.o @@ -599,10 +652,12 @@ EXTLIBS = ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease + NO_MKSTEMPS = YesPlease THREADED_DELTA_SEARCH = YesPlease endif ifeq ($(uname_S),UnixWare) @@ -614,6 +669,7 @@ ifeq ($(uname_S),UnixWare) SHELL_PATH = /usr/local/bin/bash NO_IPV6 = YesPlease NO_HSTRERROR = YesPlease + NO_MKSTEMPS = YesPlease BASIC_CFLAGS += -Kthread BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib @@ -637,6 +693,7 @@ ifeq ($(uname_S),SCO_SV) SHELL_PATH = /usr/bin/bash NO_IPV6 = YesPlease NO_HSTRERROR = YesPlease + NO_MKSTEMPS = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib NO_STRCASESTR = YesPlease @@ -655,16 +712,27 @@ ifeq ($(uname_S),Darwin) endif NO_MEMMEM = YesPlease THREADED_DELTA_SEARCH = YesPlease + USE_ST_TIMESPEC = YesPlease endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash + SANE_TOOL_PATH = /usr/xpg6/bin:/usr/xpg4/bin NO_STRCASESTR = YesPlease NO_MEMMEM = YesPlease - NO_HSTRERROR = YesPlease NO_MKDTEMP = YesPlease - OLD_ICONV = UnfortunatelyYes + NO_MKSTEMPS = YesPlease + ifeq ($(uname_R),5.7) + NEEDS_RESOLV = YesPlease + NO_IPV6 = YesPlease + NO_SOCKADDR_STORAGE = YesPlease + NO_UNSETENV = YesPlease + NO_SETENV = YesPlease + NO_STRLCPY = YesPlease + NO_C99_FORMAT = YesPlease + NO_STRTOUMAX = YesPlease + endif ifeq ($(uname_R),5.8) NO_UNSETENV = YesPlease NO_SETENV = YesPlease @@ -677,15 +745,19 @@ ifeq ($(uname_S),SunOS) NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease endif - INSTALL = ginstall + ifdef NO_IPV6 + NEEDS_RESOLV = YesPlease + endif + INSTALL = /usr/ucb/install TAR = gtar - BASIC_CFLAGS += -D__EXTENSIONS__ + BASIC_CFLAGS += -D__EXTENSIONS__ -D__sun__ endif ifeq ($(uname_O),Cygwin) NO_D_TYPE_IN_DIRENT = YesPlease NO_D_INO_IN_DIRENT = YesPlease NO_STRCASESTR = YesPlease NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease NO_SYMLINK_HEAD = YesPlease NEEDS_LIBICONV = YesPlease NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes @@ -704,6 +776,7 @@ ifeq ($(uname_S),FreeBSD) BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease + USE_ST_TIMESPEC = YesPlease THREADED_DELTA_SEARCH = YesPlease ifeq ($(shell expr "$(uname_R)" : '4\.'),2) PTHREAD_LIBS = -pthread @@ -714,6 +787,7 @@ endif ifeq ($(uname_S),OpenBSD) NO_STRCASESTR = YesPlease NO_MEMMEM = YesPlease + USE_ST_TIMESPEC = YesPlease NEEDS_LIBICONV = YesPlease BASIC_CFLAGS += -I/usr/local/include BASIC_LDFLAGS += -L/usr/local/lib @@ -726,12 +800,16 @@ ifeq ($(uname_S),NetBSD) BASIC_CFLAGS += -I/usr/pkg/include BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib THREADED_DELTA_SEARCH = YesPlease + USE_ST_TIMESPEC = YesPlease + NO_MKSTEMPS = YesPlease endif ifeq ($(uname_S),AIX) NO_STRCASESTR=YesPlease NO_MEMMEM = YesPlease NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease NO_STRLCPY = YesPlease + NO_NSEC = YesPlease FREAD_READS_DIRECTORIES = UnfortunatelyYes INTERNAL_QSORT = UnfortunatelyYes NEEDS_LIBICONV=YesPlease @@ -745,12 +823,14 @@ endif ifeq ($(uname_S),GNU) # GNU/Hurd NO_STRLCPY=YesPlease + NO_MKSTEMPS = YesPlease endif ifeq ($(uname_S),IRIX64) NO_IPV6=YesPlease NO_SETENV=YesPlease NO_STRCASESTR=YesPlease NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease NO_STRLCPY = YesPlease NO_SOCKADDR_STORAGE=YesPlease SHELL_PATH=/usr/gnu/bin/bash @@ -763,6 +843,7 @@ ifeq ($(uname_S),HP-UX) NO_SETENV=YesPlease NO_STRCASESTR=YesPlease NO_MEMMEM = YesPlease + NO_MKSTEMPS = YesPlease NO_STRLCPY = YesPlease NO_MKDTEMP = YesPlease NO_UNSETENV = YesPlease @@ -772,12 +853,13 @@ ifeq ($(uname_S),HP-UX) endif ifneq (,$(findstring CYGWIN,$(uname_S))) COMPAT_OBJS += compat/cygwin.o + UNRELIABLE_FSTAT = UnfortunatelyYes endif ifneq (,$(findstring MINGW,$(uname_S))) - NO_MMAP = YesPlease + pathsep = ; NO_PREAD = YesPlease NO_OPENSSL = YesPlease - NO_CURL = YesPlease + NO_LIBGEN_H = YesPlease NO_SYMLINK_HEAD = YesPlease NO_IPV6 = YesPlease NO_SETENV = YesPlease @@ -785,32 +867,57 @@ ifneq (,$(findstring MINGW,$(uname_S))) NO_STRCASESTR = YesPlease NO_STRLCPY = YesPlease NO_MEMMEM = YesPlease - NO_PTHREADS = YesPlease NEEDS_LIBICONV = YesPlease OLD_ICONV = YesPlease NO_C99_FORMAT = YesPlease NO_STRTOUMAX = YesPlease NO_MKDTEMP = YesPlease + NO_MKSTEMPS = YesPlease 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 + NO_NSEC = YesPlease + USE_WIN32_MMAP = YesPlease + USE_NED_ALLOCATOR = YesPlease + UNRELIABLE_FSTAT = UnfortunatelyYes + OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch - COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/regex/regex.o compat/winansi.o EXTLIBS += -lws2_32 X = .exe +ifneq (,$(wildcard ../THIS_IS_MSYSGIT)) + htmldir=doc/git/html/ + prefix = + INSTALL = /bin/install + EXTLIBS += /mingw/lib/libz.a + NO_R_TO_GCC_LINKER = YesPlease + INTERNAL_QSORT = YesPlease + THREADED_DELTA_SEARCH = YesPlease +else + NO_CURL = YesPlease + NO_PTHREADS = YesPlease +endif endif ifneq (,$(findstring arm,$(uname_M))) ARM_SHA1 = YesPlease + NO_MKSTEMPS = YesPlease endif -include config.mak.autogen -include config.mak +ifdef SANE_TOOL_PATH +SANE_TOOL_PATH_SQ = $(subst ','\'',$(SANE_TOOL_PATH)) +BROKEN_PATH_FIX = 's|^\# @@BROKEN_PATH_FIX@@$$|git_broken_path_fix $(SANE_TOOL_PATH_SQ)|' +PATH := $(SANE_TOOL_PATH):${PATH} +else +BROKEN_PATH_FIX = '/^\# @@BROKEN_PATH_FIX@@$$/d' +endif + ifeq ($(uname_S),Darwin) ifndef NO_FINK ifeq ($(shell test -d /sw/lib && echo y),y) @@ -837,6 +944,11 @@ ifndef CC_LD_DYNPATH endif endif +ifdef NO_LIBGEN_H + COMPAT_CFLAGS += -DNO_LIBGEN_H + COMPAT_OBJS += compat/basename.o +endif + ifdef NO_CURL BASIC_CFLAGS += -DNO_CURL else @@ -909,6 +1021,9 @@ endif ifdef NEEDS_NSL EXTLIBS += -lnsl endif +ifdef NEEDS_RESOLV + EXTLIBS += -lresolv +endif ifdef NO_D_TYPE_IN_DIRENT BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT endif @@ -918,6 +1033,15 @@ endif ifdef NO_ST_BLOCKS_IN_STRUCT_STAT BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT endif +ifdef USE_NSEC + BASIC_CFLAGS += -DUSE_NSEC +endif +ifdef USE_ST_TIMESPEC + BASIC_CFLAGS += -DUSE_ST_TIMESPEC +endif +ifdef NO_NSEC + BASIC_CFLAGS += -DNO_NSEC +endif ifdef NO_C99_FORMAT BASIC_CFLAGS += -DNO_C99_FORMAT endif @@ -955,6 +1079,10 @@ ifdef NO_MKDTEMP COMPAT_CFLAGS += -DNO_MKDTEMP COMPAT_OBJS += compat/mkdtemp.o endif +ifdef NO_MKSTEMPS + COMPAT_CFLAGS += -DNO_MKSTEMPS + COMPAT_OBJS += compat/mkstemps.o +endif ifdef NO_UNSETENV COMPAT_CFLAGS += -DNO_UNSETENV COMPAT_OBJS += compat/unsetenv.o @@ -965,6 +1093,14 @@ endif ifdef NO_MMAP COMPAT_CFLAGS += -DNO_MMAP COMPAT_OBJS += compat/mmap.o +else + ifdef USE_WIN32_MMAP + COMPAT_CFLAGS += -DUSE_WIN32_MMAP + COMPAT_OBJS += compat/win32mmap.o + endif +endif +ifdef OBJECT_CREATION_USES_RENAMES + COMPAT_CFLAGS += -DOBJECT_CREATION_MODE=1 endif ifdef NO_PREAD COMPAT_CFLAGS += -DNO_PREAD @@ -1061,11 +1197,23 @@ endif ifdef NO_EXTERNAL_GREP BASIC_CFLAGS += -DNO_EXTERNAL_GREP endif +ifdef UNRELIABLE_FSTAT + BASIC_CFLAGS += -DUNRELIABLE_FSTAT +endif + +ifdef USE_NED_ALLOCATOR + COMPAT_CFLAGS += -DUSE_NED_ALLOCATOR -DOVERRIDE_STRDUP -DNDEBUG -DREPLACE_SYSTEM_ALLOCATOR -Icompat/nedmalloc + COMPAT_OBJS += compat/nedmalloc/nedmalloc.o +endif ifeq ($(TCLTK_PATH),) NO_TCLTK=NoThanks endif +ifeq ($(PERL_PATH),) +NO_PERL=NoThanks +endif + QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = @@ -1132,7 +1280,7 @@ SHELL = $(SHELL_PATH) all:: shell_compatibility_test $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS) GIT-BUILD-OPTIONS ifneq (,$X) - $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';) + $(QUIET_BUILT_IN)$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$p' -ef '$p$X' || $(RM) '$p';) endif all:: @@ -1140,7 +1288,9 @@ ifndef NO_TCLTK $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) gitexecdir='$(gitexec_instdir_SQ)' all $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all endif +ifndef NO_PERL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all +endif $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) please_set_SHELL_PATH_to_a_more_modern_shell: @@ -1153,6 +1303,7 @@ strip: $(PROGRAMS) git$X git.o: git.c common-cmds.h GIT-CFLAGS $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ + '-DGIT_HTML_PATH="$(htmldir_SQ)"' \ $(ALL_CFLAGS) -c $(filter %.c,$^) git$X: git.o $(BUILTIN_OBJS) $(GITLIBS) @@ -1180,13 +1331,14 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ -e 's|@SHELL_PATH@|$(SHELL_PATH_SQ)|' \ - -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \ -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ -e 's/@@NO_CURL@@/$(NO_CURL)/g' \ + -e $(BROKEN_PATH_FIX) \ $@.sh >$@+ && \ chmod +x $@+ && \ mv $@+ $@ +ifndef NO_PERL $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL @@ -1198,7 +1350,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl sed -e '1{' \ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \ -e ' h' \ - -e ' s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ + -e ' s=.*=use lib (split(/$(pathsep)/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \ -e ' H' \ -e ' x' \ -e '}' \ @@ -1208,6 +1360,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl chmod +x $@+ && \ mv $@+ $@ +OTHER_PROGRAMS += gitweb/gitweb.cgi gitweb/gitweb.cgi: gitweb/gitweb.perl $(QUIET_GEN)$(RM) $@ $@+ && \ sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ @@ -1246,6 +1399,15 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css $@.sh > $@+ && \ chmod +x $@+ && \ mv $@+ $@ +else # NO_PERL +$(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh + $(QUIET_GEN)$(RM) $@ $@+ && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|@@REASON@@|NO_PERL=$(NO_PERL)|g' \ + unimplemented.sh >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ +endif # NO_PERL configure: configure.ac $(QUIET_GEN)$(RM) $@ $<+ && \ @@ -1361,6 +1523,8 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@ @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@ + @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@ + @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@ ### Detect Tck/Tk interpreter path changes ifndef NO_TCLTK @@ -1409,6 +1573,8 @@ test-delta$X: diff-delta.o patch-delta.o test-parse-options$X: parse-options.o +test-parse-options.o: parse-options.h + .PRECIOUS: $(patsubst test-%$X,test-%.o,$(TEST_PROGRAMS)) test-%$X: test-%.o $(GITLIBS) @@ -1455,17 +1621,20 @@ install: all $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install +ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install +endif ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install endif ifneq (,$X) - $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';) + $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';) endif bindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \ execdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \ { $(RM) "$$execdir/git-add$X" && \ + test -z "$(NO_CROSS_DIRECTORY_HARDLINKS)" && \ 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 \ @@ -1473,7 +1642,7 @@ endif 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 } && \ + done; } && \ ./check_bindir "z$$bindir" "z$$execdir" "$$bindir/git-add$X" install-doc: @@ -1553,7 +1722,7 @@ distclean: clean $(RM) configure clean: - $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \ + $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \ $(LIB_FILE) $(XDIFF_LIB) $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X $(RM) $(TEST_PROGRAMS) @@ -1563,9 +1732,11 @@ clean: $(RM) -r $(GIT_TARNAME) .doc-tmp-dir $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz $(RM) $(htmldocs).tar.gz $(manpages).tar.gz - $(RM) gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean +ifndef NO_PERL + $(RM) gitweb/gitweb.cgi $(MAKE) -C perl clean +endif $(MAKE) -C templates/ clean $(MAKE) -C t/ clean ifndef NO_TCLTK @@ -1638,3 +1809,27 @@ check-docs:: check-builtins:: ./check-builtins.sh +### Test suite coverage testing +# +.PHONY: coverage coverage-clean coverage-build coverage-report + +coverage: + $(MAKE) coverage-build + $(MAKE) coverage-report + +coverage-clean: + rm -f *.gcda *.gcno + +COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs +COVERAGE_LDFLAGS = $(CFLAGS) -O0 -lgcov + +coverage-build: coverage-clean + $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all + $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \ + -j1 test + +coverage-report: + gcov -b *.c + grep '^function.*called 0 ' *.c.gcov \ + | sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \ + | tee coverage-untested-functions @@ -1 +1 @@ -Documentation/RelNotes-1.6.2.5.txt
\ No newline at end of file +Documentation/RelNotes-1.6.4.txt
\ No newline at end of file @@ -27,7 +27,7 @@ int split_cmdline(char *cmdline, const char ***argv) int src, dst, count = 0, size = 16; char quoted = 0; - *argv = xmalloc(sizeof(char*) * size); + *argv = xmalloc(sizeof(char *) * size); /* split alias_string */ (*argv)[count++] = cmdline; @@ -38,10 +38,7 @@ int split_cmdline(char *cmdline, const char ***argv) while (cmdline[++src] && isspace(cmdline[src])) ; /* skip */ - if (count >= size) { - size += 16; - *argv = xrealloc(*argv, sizeof(char*) * size); - } + ALLOC_GROW(*argv, count+1, size); (*argv)[count++] = cmdline + dst; } else if (!quoted && (c == '\'' || c == '"')) { quoted = c; @@ -72,6 +69,9 @@ int split_cmdline(char *cmdline, const char ***argv) return error("unclosed quote"); } + ALLOC_GROW(*argv, count+1, size); + (*argv)[count] = NULL; + return count; } @@ -57,7 +57,7 @@ DEFINE_ALLOCATOR(object, union any_object) #define SZ_FMT "%zu" #endif -static void report(const char* name, unsigned int count, size_t size) +static void report(const char *name, unsigned int count, size_t size) { fprintf(stderr, "%10s: %8u (" SZ_FMT " kB)\n", name, count, size); } diff --git a/archive-tar.c b/archive-tar.c index ba890ebdec..cee06ce3cb 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -180,7 +180,7 @@ static int write_tar_entry(struct archiver_args *args, sprintf(header.mode, "%07o", mode & 07777); sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0); - sprintf(header.mtime, "%011lo", args->time); + sprintf(header.mtime, "%011lo", (unsigned long) args->time); sprintf(header.uid, "%07o", 0); sprintf(header.gid, "%07o", 0); @@ -4,6 +4,7 @@ #include "attr.h" #include "archive.h" #include "parse-options.h" +#include "unpack-trees.h" static char const * const archive_usage[] = { "git archive [options] <tree-ish> [path...]", @@ -150,6 +151,8 @@ int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry) { struct archiver_context context; + struct unpack_trees_options opts; + struct tree_desc t; int err; if (args->baselen > 0 && args->base[args->baselen - 1] == '/') { @@ -168,6 +171,22 @@ int write_archive_entries(struct archiver_args *args, context.args = args; context.write_entry = write_entry; + /* + * Setup index and instruct attr to read index only + */ + if (!args->worktree_attributes) { + memset(&opts, 0, sizeof(opts)); + opts.index_only = 1; + opts.head_idx = -1; + opts.src_index = &the_index; + opts.dst_index = &the_index; + opts.fn = oneway_merge; + init_tree_desc(&t, args->tree->buffer, args->tree->size); + if (unpack_trees(1, &t, &opts)) + return -1; + git_attr_set_direction(GIT_ATTR_INDEX, &the_index); + } + err = read_tree_recursive(args->tree, args->base, args->baselen, 0, args->pathspec, write_archive_entry, &context); if (err == READ_TREE_RECURSIVE) @@ -253,15 +272,21 @@ static int parse_archive_args(int argc, const char **argv, const char *base = NULL; const char *remote = NULL; const char *exec = NULL; + const char *output = NULL; int compression_level = -1; int verbose = 0; int i; int list = 0; + int worktree_attributes = 0; struct option opts[] = { OPT_GROUP(""), OPT_STRING(0, "format", &format, "fmt", "archive format"), OPT_STRING(0, "prefix", &base, "prefix", "prepend prefix to each pathname in the archive"), + OPT_STRING(0, "output", &output, "file", + "write the archive to this file"), + OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes, + "read .gitattributes in working directory"), OPT__VERBOSE(&verbose), OPT__COMPR('0', &compression_level, "store only", 0), OPT__COMPR('1', &compression_level, "compress faster", 1), @@ -284,12 +309,14 @@ static int parse_archive_args(int argc, const char **argv, OPT_END() }; - argc = parse_options(argc, argv, opts, archive_usage, 0); + argc = parse_options(argc, argv, NULL, opts, archive_usage, 0); if (remote) die("Unexpected option --remote"); if (exec) die("Option --exec can only be used together with --remote"); + if (output) + die("Unexpected option --output"); if (!base) base = ""; @@ -319,6 +346,7 @@ static int parse_archive_args(int argc, const char **argv, args->verbose = verbose; args->base = base; args->baselen = strlen(base); + args->worktree_attributes = worktree_attributes; return argc; } @@ -10,6 +10,7 @@ struct archiver_args { time_t time; const char **pathspec; unsigned int verbose : 1; + unsigned int worktree_attributes : 1; int compression_level; }; @@ -1,3 +1,4 @@ +#define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "attr.h" @@ -34,8 +35,7 @@ static struct git_attr *(git_attr_hash[HASHSIZE]); static unsigned hash_name(const char *name, int namelen) { - unsigned val = 0; - unsigned char c; + unsigned val = 0, c; while (namelen--) { c = *name++; @@ -223,7 +223,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, if (is_macro) res->u.attr = git_attr(name, namelen); else { - res->u.pattern = (char*)&(res->state[num_attr]); + res->u.pattern = (char *)&(res->state[num_attr]); memcpy(res->u.pattern, name, namelen); res->u.pattern[namelen] = 0; } @@ -274,7 +274,7 @@ static void free_attr_elem(struct attr_stack *e) setto == ATTR__UNKNOWN) ; else - free((char*) setto); + free((char *) setto); } free(a); } @@ -318,6 +318,9 @@ static struct attr_stack *read_attr_from_array(const char **list) return res; } +static enum git_attr_direction direction; +static struct index_state *use_index; + static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) { FILE *fp = fopen(path, "r"); @@ -340,9 +343,10 @@ static void *read_index_data(const char *path) unsigned long sz; enum object_type type; void *data; + struct index_state *istate = use_index ? use_index : &the_index; len = strlen(path); - pos = cache_name_pos(path, len); + pos = index_name_pos(istate, path, len); if (pos < 0) { /* * We might be in the middle of a merge, in which @@ -350,15 +354,15 @@ static void *read_index_data(const char *path) */ int i; for (i = -pos - 1; - (pos < 0 && i < active_nr && - !strcmp(active_cache[i]->name, path)); + (pos < 0 && i < istate->cache_nr && + !strcmp(istate->cache[i]->name, path)); i++) - if (ce_stage(active_cache[i]) == 2) + if (ce_stage(istate->cache[i]) == 2) pos = i; } if (pos < 0) return NULL; - data = read_sha1_file(active_cache[pos]->sha1, &type, &sz); + data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz); if (!data || type != OBJ_BLOB) { free(data); return NULL; @@ -366,27 +370,17 @@ static void *read_index_data(const char *path) return data; } -static struct attr_stack *read_attr(const char *path, int macro_ok) +static struct attr_stack *read_attr_from_index(const char *path, int macro_ok) { struct attr_stack *res; char *buf, *sp; int lineno = 0; - res = read_attr_from_file(path, macro_ok); - if (res) - return res; - - res = xcalloc(1, sizeof(*res)); - - /* - * There is no checked out .gitattributes file there, but - * we might have it in the index. We allow operation in a - * sparsely checked out work tree, so read from it. - */ buf = read_index_data(path); if (!buf) - return res; + return NULL; + res = xcalloc(1, sizeof(*res)); for (sp = buf; *sp; ) { char *ep; int more; @@ -401,6 +395,32 @@ static struct attr_stack *read_attr(const char *path, int macro_ok) return res; } +static struct attr_stack *read_attr(const char *path, int macro_ok) +{ + struct attr_stack *res; + + if (direction == GIT_ATTR_CHECKOUT) { + res = read_attr_from_index(path, macro_ok); + if (!res) + res = read_attr_from_file(path, macro_ok); + } + else if (direction == GIT_ATTR_CHECKIN) { + res = read_attr_from_file(path, macro_ok); + if (!res) + /* + * There is no checked out .gitattributes file there, but + * we might have it in the index. We allow operation in a + * sparsely checked out work tree, so read from it. + */ + res = read_attr_from_index(path, macro_ok); + } + else + res = read_attr_from_index(path, macro_ok); + if (!res) + res = xcalloc(1, sizeof(*res)); + return res; +} + #if DEBUG_ATTR static void debug_info(const char *what, struct attr_stack *elem) { @@ -428,6 +448,15 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr #define debug_set(a,b,c,d) do { ; } while (0) #endif +static void drop_attr_stack(void) +{ + while (attr_stack) { + struct attr_stack *elem = attr_stack; + attr_stack = elem->prev; + free_attr_elem(elem); + } +} + static void bootstrap_attr_stack(void) { if (!attr_stack) { @@ -438,7 +467,7 @@ static void bootstrap_attr_stack(void) elem->prev = attr_stack; attr_stack = elem; - if (!is_bare_repository()) { + if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { elem = read_attr(GITATTRIBUTES_FILE, 1); elem->origin = strdup(""); elem->prev = attr_stack; @@ -505,7 +534,7 @@ static void prepare_attr_stack(const char *path, int dirlen) /* * Read from parent directories and push them down */ - if (!is_bare_repository()) { + if (!is_bare_repository() || direction == GIT_ATTR_INDEX) { while (1) { char *cp; @@ -642,3 +671,16 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check) return 0; } + +void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate) +{ + enum git_attr_direction old = direction; + + if (is_bare_repository() && new != GIT_ATTR_INDEX) + die("BUG: non-INDEX attr direction in a bare repo"); + + direction = new; + if (new != old) + drop_attr_stack(); + use_index = istate; +} @@ -31,4 +31,11 @@ struct git_attr_check { int git_checkattr(const char *path, int, struct git_attr_check *); +enum git_attr_direction { + GIT_ATTR_CHECKIN, + GIT_ATTR_CHECKOUT, + GIT_ATTR_INDEX, +}; +void git_attr_set_direction(enum git_attr_direction, struct index_state *); + #endif /* ATTR_H */ @@ -91,7 +91,7 @@ void encode_85(char *buf, const unsigned char *data, int bytes) unsigned acc = 0; int cnt; for (cnt = 24; cnt >= 0; cnt -= 8) { - int ch = *data++; + unsigned ch = *data++; acc |= ch << cnt; if (--bytes == 0) break; diff --git a/bisect.c b/bisect.c new file mode 100644 index 0000000000..6fdff05722 --- /dev/null +++ b/bisect.c @@ -0,0 +1,979 @@ +#include "cache.h" +#include "commit.h" +#include "diff.h" +#include "revision.h" +#include "refs.h" +#include "list-objects.h" +#include "quote.h" +#include "sha1-lookup.h" +#include "run-command.h" +#include "log-tree.h" +#include "bisect.h" + +struct sha1_array { + unsigned char (*sha1)[20]; + int sha1_nr; + int sha1_alloc; + int sorted; +}; + +static struct sha1_array good_revs; +static struct sha1_array skipped_revs; + +static const unsigned char *current_bad_sha1; + +struct argv_array { + const char **argv; + int argv_nr; + int argv_alloc; +}; + +static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL}; +static const char *argv_show_branch[] = {"show-branch", NULL, NULL}; + +/* bits #0-15 in revision.h */ + +#define COUNTED (1u<<16) + +/* + * This is a truly stupid algorithm, but it's only + * used for bisection, and we just don't care enough. + * + * We care just barely enough to avoid recursing for + * non-merge entries. + */ +static int count_distance(struct commit_list *entry) +{ + int nr = 0; + + while (entry) { + struct commit *commit = entry->item; + struct commit_list *p; + + if (commit->object.flags & (UNINTERESTING | COUNTED)) + break; + if (!(commit->object.flags & TREESAME)) + nr++; + commit->object.flags |= COUNTED; + p = commit->parents; + entry = p; + if (p) { + p = p->next; + while (p) { + nr += count_distance(p); + p = p->next; + } + } + } + + return nr; +} + +static void clear_distance(struct commit_list *list) +{ + while (list) { + struct commit *commit = list->item; + commit->object.flags &= ~COUNTED; + list = list->next; + } +} + +#define DEBUG_BISECT 0 + +static inline int weight(struct commit_list *elem) +{ + return *((int*)(elem->item->util)); +} + +static inline void weight_set(struct commit_list *elem, int weight) +{ + *((int*)(elem->item->util)) = weight; +} + +static int count_interesting_parents(struct commit *commit) +{ + struct commit_list *p; + int count; + + for (count = 0, p = commit->parents; p; p = p->next) { + if (p->item->object.flags & UNINTERESTING) + continue; + count++; + } + return count; +} + +static inline int halfway(struct commit_list *p, int nr) +{ + /* + * Don't short-cut something we are not going to return! + */ + if (p->item->object.flags & TREESAME) + return 0; + if (DEBUG_BISECT) + return 0; + /* + * 2 and 3 are halfway of 5. + * 3 is halfway of 6 but 2 and 4 are not. + */ + switch (2 * weight(p) - nr) { + case -1: case 0: case 1: + return 1; + default: + return 0; + } +} + +#if !DEBUG_BISECT +#define show_list(a,b,c,d) do { ; } while (0) +#else +static void show_list(const char *debug, int counted, int nr, + struct commit_list *list) +{ + struct commit_list *p; + + fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr); + + for (p = list; p; p = p->next) { + struct commit_list *pp; + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + enum object_type type; + unsigned long size; + char *buf = read_sha1_file(commit->object.sha1, &type, &size); + char *ep, *sp; + + fprintf(stderr, "%c%c%c ", + (flags & TREESAME) ? ' ' : 'T', + (flags & UNINTERESTING) ? 'U' : ' ', + (flags & COUNTED) ? 'C' : ' '); + if (commit->util) + fprintf(stderr, "%3d", weight(p)); + else + fprintf(stderr, "---"); + fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1)); + for (pp = commit->parents; pp; pp = pp->next) + fprintf(stderr, " %.*s", 8, + sha1_to_hex(pp->item->object.sha1)); + + sp = strstr(buf, "\n\n"); + if (sp) { + sp += 2; + for (ep = sp; *ep && *ep != '\n'; ep++) + ; + fprintf(stderr, " %.*s", (int)(ep - sp), sp); + } + fprintf(stderr, "\n"); + } +} +#endif /* DEBUG_BISECT */ + +static struct commit_list *best_bisection(struct commit_list *list, int nr) +{ + struct commit_list *p, *best; + int best_distance = -1; + + best = list; + for (p = list; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (flags & TREESAME) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + if (distance > best_distance) { + best = p; + best_distance = distance; + } + } + + return best; +} + +struct commit_dist { + struct commit *commit; + int distance; +}; + +static int compare_commit_dist(const void *a_, const void *b_) +{ + struct commit_dist *a, *b; + + a = (struct commit_dist *)a_; + b = (struct commit_dist *)b_; + if (a->distance != b->distance) + return b->distance - a->distance; /* desc sort */ + return hashcmp(a->commit->object.sha1, b->commit->object.sha1); +} + +static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr) +{ + struct commit_list *p; + struct commit_dist *array = xcalloc(nr, sizeof(*array)); + int cnt, i; + + for (p = list, cnt = 0; p; p = p->next) { + int distance; + unsigned flags = p->item->object.flags; + + if (flags & TREESAME) + continue; + distance = weight(p); + if (nr - distance < distance) + distance = nr - distance; + array[cnt].commit = p->item; + array[cnt].distance = distance; + cnt++; + } + qsort(array, cnt, sizeof(*array), compare_commit_dist); + for (p = list, i = 0; i < cnt; i++) { + struct name_decoration *r = xmalloc(sizeof(*r) + 100); + struct object *obj = &(array[i].commit->object); + + sprintf(r->name, "dist=%d", array[i].distance); + r->next = add_decoration(&name_decoration, obj, r); + p->item = array[i].commit; + p = p->next; + } + if (p) + p->next = NULL; + free(array); + return list; +} + +/* + * zero or positive weight is the number of interesting commits it can + * reach, including itself. Especially, weight = 0 means it does not + * reach any tree-changing commits (e.g. just above uninteresting one + * but traversal is with pathspec). + * + * weight = -1 means it has one parent and its distance is yet to + * be computed. + * + * weight = -2 means it has more than one parent and its distance is + * unknown. After running count_distance() first, they will get zero + * or positive distance. + */ +static struct commit_list *do_find_bisection(struct commit_list *list, + int nr, int *weights, + int find_all) +{ + int n, counted; + struct commit_list *p; + + counted = 0; + + for (n = 0, p = list; p; p = p->next) { + struct commit *commit = p->item; + unsigned flags = commit->object.flags; + + p->item->util = &weights[n++]; + switch (count_interesting_parents(commit)) { + case 0: + if (!(flags & TREESAME)) { + weight_set(p, 1); + counted++; + show_list("bisection 2 count one", + counted, nr, list); + } + /* + * otherwise, it is known not to reach any + * tree-changing commit and gets weight 0. + */ + break; + case 1: + weight_set(p, -1); + break; + default: + weight_set(p, -2); + break; + } + } + + show_list("bisection 2 initialize", counted, nr, list); + + /* + * If you have only one parent in the resulting set + * then you can reach one commit more than that parent + * can reach. So we do not have to run the expensive + * count_distance() for single strand of pearls. + * + * However, if you have more than one parents, you cannot + * just add their distance and one for yourself, since + * they usually reach the same ancestor and you would + * end up counting them twice that way. + * + * So we will first count distance of merges the usual + * way, and then fill the blanks using cheaper algorithm. + */ + for (p = list; p; p = p->next) { + if (p->item->object.flags & UNINTERESTING) + continue; + if (weight(p) != -2) + continue; + weight_set(p, count_distance(p)); + clear_distance(list); + + /* Does it happen to be at exactly half-way? */ + if (!find_all && halfway(p, nr)) + return p; + counted++; + } + + show_list("bisection 2 count_distance", counted, nr, list); + + while (counted < nr) { + for (p = list; p; p = p->next) { + struct commit_list *q; + unsigned flags = p->item->object.flags; + + if (0 <= weight(p)) + continue; + for (q = p->item->parents; q; q = q->next) { + if (q->item->object.flags & UNINTERESTING) + continue; + if (0 <= weight(q)) + break; + } + if (!q) + continue; + + /* + * weight for p is unknown but q is known. + * add one for p itself if p is to be counted, + * otherwise inherit it from q directly. + */ + if (!(flags & TREESAME)) { + weight_set(p, weight(q)+1); + counted++; + show_list("bisection 2 count one", + counted, nr, list); + } + else + weight_set(p, weight(q)); + + /* Does it happen to be at exactly half-way? */ + if (!find_all && halfway(p, nr)) + return p; + } + } + + show_list("bisection 2 counted all", counted, nr, list); + + if (!find_all) + return best_bisection(list, nr); + else + return best_bisection_sorted(list, nr); +} + +struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all, + int find_all) +{ + int nr, on_list; + struct commit_list *p, *best, *next, *last; + int *weights; + + show_list("bisection 2 entry", 0, 0, list); + + /* + * Count the number of total and tree-changing items on the + * list, while reversing the list. + */ + for (nr = on_list = 0, last = NULL, p = list; + p; + p = next) { + unsigned flags = p->item->object.flags; + + next = p->next; + if (flags & UNINTERESTING) + continue; + p->next = last; + last = p; + if (!(flags & TREESAME)) + nr++; + on_list++; + } + list = last; + show_list("bisection 2 sorted", 0, nr, list); + + *all = nr; + weights = xcalloc(on_list, sizeof(*weights)); + + /* Do the real work of finding bisection commit. */ + best = do_find_bisection(list, nr, weights, find_all); + if (best) { + if (!find_all) + best->next = NULL; + *reaches = weight(best); + } + free(weights); + return best; +} + +static void argv_array_push(struct argv_array *array, const char *string) +{ + ALLOC_GROW(array->argv, array->argv_nr + 1, array->argv_alloc); + array->argv[array->argv_nr++] = string; +} + +static void argv_array_push_sha1(struct argv_array *array, + const unsigned char *sha1, + const char *format) +{ + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, format, sha1_to_hex(sha1)); + argv_array_push(array, strbuf_detach(&buf, NULL)); +} + +static void sha1_array_push(struct sha1_array *array, + const unsigned char *sha1) +{ + ALLOC_GROW(array->sha1, array->sha1_nr + 1, array->sha1_alloc); + hashcpy(array->sha1[array->sha1_nr++], sha1); +} + +static int register_ref(const char *refname, const unsigned char *sha1, + int flags, void *cb_data) +{ + if (!strcmp(refname, "bad")) { + current_bad_sha1 = sha1; + } else if (!prefixcmp(refname, "good-")) { + sha1_array_push(&good_revs, sha1); + } else if (!prefixcmp(refname, "skip-")) { + sha1_array_push(&skipped_revs, sha1); + } + + return 0; +} + +static int read_bisect_refs(void) +{ + return for_each_ref_in("refs/bisect/", register_ref, NULL); +} + +void read_bisect_paths(struct argv_array *array) +{ + struct strbuf str = STRBUF_INIT; + const char *filename = git_path("BISECT_NAMES"); + FILE *fp = fopen(filename, "r"); + + if (!fp) + die("Could not open file '%s': %s", filename, strerror(errno)); + + while (strbuf_getline(&str, fp, '\n') != EOF) { + char *quoted; + int res; + + strbuf_trim(&str); + quoted = strbuf_detach(&str, NULL); + res = sq_dequote_to_argv(quoted, &array->argv, + &array->argv_nr, &array->argv_alloc); + if (res) + die("Badly quoted content in file '%s': %s", + filename, quoted); + } + + strbuf_release(&str); + fclose(fp); +} + +static int array_cmp(const void *a, const void *b) +{ + return hashcmp(a, b); +} + +static void sort_sha1_array(struct sha1_array *array) +{ + qsort(array->sha1, array->sha1_nr, sizeof(*array->sha1), array_cmp); + + array->sorted = 1; +} + +static const unsigned char *sha1_access(size_t index, void *table) +{ + unsigned char (*array)[20] = table; + return array[index]; +} + +static int lookup_sha1_array(struct sha1_array *array, + const unsigned char *sha1) +{ + if (!array->sorted) + sort_sha1_array(array); + + return sha1_pos(sha1, array->sha1, array->sha1_nr, sha1_access); +} + +static char *join_sha1_array_hex(struct sha1_array *array, char delim) +{ + struct strbuf joined_hexs = STRBUF_INIT; + int i; + + for (i = 0; i < array->sha1_nr; i++) { + strbuf_addstr(&joined_hexs, sha1_to_hex(array->sha1[i])); + if (i + 1 < array->sha1_nr) + strbuf_addch(&joined_hexs, delim); + } + + return strbuf_detach(&joined_hexs, NULL); +} + +/* + * In this function, passing a not NULL skipped_first is very special. + * It means that we want to know if the first commit in the list is + * skipped because we will want to test a commit away from it if it is + * indeed skipped. + * So if the first commit is skipped, we cannot take the shortcut to + * just "return list" when we find the first non skipped commit, we + * have to return a fully filtered list. + * + * We use (*skipped_first == -1) to mean "it has been found that the + * first commit is not skipped". In this case *skipped_first is set back + * to 0 just before the function returns. + */ +struct commit_list *filter_skipped(struct commit_list *list, + struct commit_list **tried, + int show_all, + int *count, + int *skipped_first) +{ + struct commit_list *filtered = NULL, **f = &filtered; + + *tried = NULL; + + if (skipped_first) + *skipped_first = 0; + if (count) + *count = 0; + + if (!skipped_revs.sha1_nr) + return list; + + while (list) { + struct commit_list *next = list->next; + list->next = NULL; + if (0 <= lookup_sha1_array(&skipped_revs, + list->item->object.sha1)) { + if (skipped_first && !*skipped_first) + *skipped_first = 1; + /* Move current to tried list */ + *tried = list; + tried = &list->next; + } else { + if (!show_all) { + if (!skipped_first || !*skipped_first) + return list; + } else if (skipped_first && !*skipped_first) { + /* This means we know it's not skipped */ + *skipped_first = -1; + } + /* Move current to filtered list */ + *f = list; + f = &list->next; + if (count) + (*count)++; + } + list = next; + } + + if (skipped_first && *skipped_first == -1) + *skipped_first = 0; + + return filtered; +} + +static struct commit_list *apply_skip_ratio(struct commit_list *list, + int count, + int skip_num, int skip_denom) +{ + int index, i; + struct commit_list *cur, *previous; + + cur = list; + previous = NULL; + index = count * skip_num / skip_denom; + + for (i = 0; cur; cur = cur->next, i++) { + if (i == index) { + if (hashcmp(cur->item->object.sha1, current_bad_sha1)) + return cur; + if (previous) + return previous; + return list; + } + previous = cur; + } + + return list; +} + +static struct commit_list *managed_skipped(struct commit_list *list, + struct commit_list **tried) +{ + int count, skipped_first; + int skip_num, skip_denom; + + *tried = NULL; + + if (!skipped_revs.sha1_nr) + return list; + + list = filter_skipped(list, tried, 0, &count, &skipped_first); + + if (!skipped_first) + return list; + + /* Use alternatively 1/5, 2/5 and 3/5 as skip ratio. */ + skip_num = count % 3 + 1; + skip_denom = 5; + + return apply_skip_ratio(list, count, skip_num, skip_denom); +} + +static void bisect_rev_setup(struct rev_info *revs, const char *prefix, + const char *bad_format, const char *good_format, + int read_paths) +{ + struct argv_array rev_argv = { NULL, 0, 0 }; + int i; + + init_revisions(revs, prefix); + revs->abbrev = 0; + revs->commit_format = CMIT_FMT_UNSPECIFIED; + + /* rev_argv.argv[0] will be ignored by setup_revisions */ + argv_array_push(&rev_argv, xstrdup("bisect_rev_setup")); + argv_array_push_sha1(&rev_argv, current_bad_sha1, bad_format); + for (i = 0; i < good_revs.sha1_nr; i++) + argv_array_push_sha1(&rev_argv, good_revs.sha1[i], + good_format); + argv_array_push(&rev_argv, xstrdup("--")); + if (read_paths) + read_bisect_paths(&rev_argv); + argv_array_push(&rev_argv, NULL); + + setup_revisions(rev_argv.argv_nr, rev_argv.argv, revs, NULL); +} + +static void bisect_common(struct rev_info *revs) +{ + if (prepare_revision_walk(revs)) + die("revision walk setup failed"); + if (revs->tree_objects) + mark_edges_uninteresting(revs->commits, revs, NULL); +} + +static void exit_if_skipped_commits(struct commit_list *tried, + const unsigned char *bad) +{ + if (!tried) + return; + + printf("There are only 'skip'ped commits left to test.\n" + "The first bad commit could be any of:\n"); + print_commit_list(tried, "%s\n", "%s\n"); + if (bad) + printf("%s\n", sha1_to_hex(bad)); + printf("We cannot bisect more!\n"); + exit(2); +} + +static int is_expected_rev(const unsigned char *sha1) +{ + const char *filename = git_path("BISECT_EXPECTED_REV"); + struct stat st; + struct strbuf str = STRBUF_INIT; + FILE *fp; + int res = 0; + + if (stat(filename, &st) || !S_ISREG(st.st_mode)) + return 0; + + fp = fopen(filename, "r"); + if (!fp) + return 0; + + if (strbuf_getline(&str, fp, '\n') != EOF) + res = !strcmp(str.buf, sha1_to_hex(sha1)); + + strbuf_release(&str); + fclose(fp); + + return res; +} + +static void mark_expected_rev(char *bisect_rev_hex) +{ + int len = strlen(bisect_rev_hex); + const char *filename = git_path("BISECT_EXPECTED_REV"); + int fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + + if (fd < 0) + die("could not create file '%s': %s", + filename, strerror(errno)); + + bisect_rev_hex[len] = '\n'; + write_or_die(fd, bisect_rev_hex, len + 1); + bisect_rev_hex[len] = '\0'; + + if (close(fd) < 0) + die("closing file %s: %s", filename, strerror(errno)); +} + +static int bisect_checkout(char *bisect_rev_hex) +{ + int res; + + mark_expected_rev(bisect_rev_hex); + + argv_checkout[2] = bisect_rev_hex; + res = run_command_v_opt(argv_checkout, RUN_GIT_CMD); + if (res) + exit(res); + + argv_show_branch[1] = bisect_rev_hex; + return run_command_v_opt(argv_show_branch, RUN_GIT_CMD); +} + +static struct commit *get_commit_reference(const unsigned char *sha1) +{ + struct commit *r = lookup_commit_reference(sha1); + if (!r) + die("Not a valid commit name %s", sha1_to_hex(sha1)); + return r; +} + +static struct commit **get_bad_and_good_commits(int *rev_nr) +{ + int len = 1 + good_revs.sha1_nr; + struct commit **rev = xmalloc(len * sizeof(*rev)); + int i, n = 0; + + rev[n++] = get_commit_reference(current_bad_sha1); + for (i = 0; i < good_revs.sha1_nr; i++) + rev[n++] = get_commit_reference(good_revs.sha1[i]); + *rev_nr = n; + + return rev; +} + +static void handle_bad_merge_base(void) +{ + if (is_expected_rev(current_bad_sha1)) { + char *bad_hex = sha1_to_hex(current_bad_sha1); + char *good_hex = join_sha1_array_hex(&good_revs, ' '); + + fprintf(stderr, "The merge base %s is bad.\n" + "This means the bug has been fixed " + "between %s and [%s].\n", + bad_hex, bad_hex, good_hex); + + exit(3); + } + + fprintf(stderr, "Some good revs are not ancestor of the bad rev.\n" + "git bisect cannot work properly in this case.\n" + "Maybe you mistake good and bad revs?\n"); + exit(1); +} + +void handle_skipped_merge_base(const unsigned char *mb) +{ + char *mb_hex = sha1_to_hex(mb); + char *bad_hex = sha1_to_hex(current_bad_sha1); + char *good_hex = join_sha1_array_hex(&good_revs, ' '); + + fprintf(stderr, "Warning: the merge base between %s and [%s] " + "must be skipped.\n" + "So we cannot be sure the first bad commit is " + "between %s and %s.\n" + "We continue anyway.\n", + bad_hex, good_hex, mb_hex, bad_hex); + free(good_hex); +} + +/* + * "check_merge_bases" checks that merge bases are not "bad". + * + * - If one is "bad", it means the user assumed something wrong + * and we must exit with a non 0 error code. + * - If one is "good", that's good, we have nothing to do. + * - If one is "skipped", we can't know but we should warn. + * - If we don't know, we should check it out and ask the user to test. + */ +static void check_merge_bases(void) +{ + struct commit_list *result; + int rev_nr; + struct commit **rev = get_bad_and_good_commits(&rev_nr); + + result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1, 0); + + for (; result; result = result->next) { + const unsigned char *mb = result->item->object.sha1; + if (!hashcmp(mb, current_bad_sha1)) { + handle_bad_merge_base(); + } else if (0 <= lookup_sha1_array(&good_revs, mb)) { + continue; + } else if (0 <= lookup_sha1_array(&skipped_revs, mb)) { + handle_skipped_merge_base(mb); + } else { + printf("Bisecting: a merge base must be tested\n"); + exit(bisect_checkout(sha1_to_hex(mb))); + } + } + + free(rev); + free_commit_list(result); +} + +static int check_ancestors(const char *prefix) +{ + struct rev_info revs; + struct object_array pending_copy; + int i, res; + + bisect_rev_setup(&revs, prefix, "^%s", "%s", 0); + + /* Save pending objects, so they can be cleaned up later. */ + memset(&pending_copy, 0, sizeof(pending_copy)); + for (i = 0; i < revs.pending.nr; i++) + add_object_array(revs.pending.objects[i].item, + revs.pending.objects[i].name, + &pending_copy); + + bisect_common(&revs); + res = (revs.commits != NULL); + + /* Clean up objects used, as they will be reused. */ + for (i = 0; i < pending_copy.nr; i++) { + struct object *o = pending_copy.objects[i].item; + clear_commit_marks((struct commit *)o, ALL_REV_FLAGS); + } + + return res; +} + +/* + * "check_good_are_ancestors_of_bad" checks that all "good" revs are + * ancestor of the "bad" rev. + * + * If that's not the case, we need to check the merge bases. + * If a merge base must be tested by the user, its source code will be + * checked out to be tested by the user and we will exit. + */ +static void check_good_are_ancestors_of_bad(const char *prefix) +{ + const char *filename = git_path("BISECT_ANCESTORS_OK"); + struct stat st; + int fd; + + if (!current_bad_sha1) + die("a bad revision is needed"); + + /* Check if file BISECT_ANCESTORS_OK exists. */ + if (!stat(filename, &st) && S_ISREG(st.st_mode)) + return; + + /* Bisecting with no good rev is ok. */ + if (good_revs.sha1_nr == 0) + return; + + /* Check if all good revs are ancestor of the bad rev. */ + if (check_ancestors(prefix)) + check_merge_bases(); + + /* Create file BISECT_ANCESTORS_OK. */ + fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd < 0) + warning("could not create file '%s': %s", + filename, strerror(errno)); + else + close(fd); +} + +/* + * This does "git diff-tree --pretty COMMIT" without one fork+exec. + */ +static void show_diff_tree(const char *prefix, struct commit *commit) +{ + struct rev_info opt; + + /* diff-tree init */ + init_revisions(&opt, prefix); + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ + opt.abbrev = 0; + opt.diff = 1; + + /* This is what "--pretty" does */ + opt.verbose_header = 1; + opt.use_terminator = 0; + opt.commit_format = CMIT_FMT_DEFAULT; + + /* diff-tree init */ + if (!opt.diffopt.output_format) + opt.diffopt.output_format = DIFF_FORMAT_RAW; + + log_tree_commit(&opt, commit); +} + +/* + * We use the convention that exiting with an exit code 10 means that + * the bisection process finished successfully. + * In this case the calling shell script should exit 0. + */ +int bisect_next_all(const char *prefix) +{ + struct rev_info revs; + struct commit_list *tried; + int reaches = 0, all = 0, nr; + const unsigned char *bisect_rev; + char bisect_rev_hex[41]; + + if (read_bisect_refs()) + die("reading bisect refs failed"); + + check_good_are_ancestors_of_bad(prefix); + + bisect_rev_setup(&revs, prefix, "%s", "^%s", 1); + revs.limited = 1; + + bisect_common(&revs); + + revs.commits = find_bisection(revs.commits, &reaches, &all, + !!skipped_revs.sha1_nr); + revs.commits = managed_skipped(revs.commits, &tried); + + if (!revs.commits) { + /* + * We should exit here only if the "bad" + * commit is also a "skip" commit. + */ + exit_if_skipped_commits(tried, NULL); + + printf("%s was both good and bad\n", + sha1_to_hex(current_bad_sha1)); + exit(1); + } + + bisect_rev = revs.commits->item->object.sha1; + memcpy(bisect_rev_hex, sha1_to_hex(bisect_rev), 41); + + if (!hashcmp(bisect_rev, current_bad_sha1)) { + exit_if_skipped_commits(tried, current_bad_sha1); + printf("%s is first bad commit\n", bisect_rev_hex); + show_diff_tree(prefix, revs.commits->item); + /* This means the bisection process succeeded. */ + exit(10); + } + + nr = all - reaches - 1; + printf("Bisecting: %d revisions left to test after this " + "(roughly %d steps)\n", nr, estimate_bisect_steps(all)); + + return bisect_checkout(bisect_rev_hex); +} + diff --git a/bisect.h b/bisect.h new file mode 100644 index 0000000000..82f8fc1910 --- /dev/null +++ b/bisect.h @@ -0,0 +1,36 @@ +#ifndef BISECT_H +#define BISECT_H + +extern struct commit_list *find_bisection(struct commit_list *list, + int *reaches, int *all, + int find_all); + +extern struct commit_list *filter_skipped(struct commit_list *list, + struct commit_list **tried, + int show_all, + int *count, + int *skipped_first); + +extern void print_commit_list(struct commit_list *list, + const char *format_cur, + const char *format_last); + +/* bisect_show_flags flags in struct rev_list_info */ +#define BISECT_SHOW_ALL (1<<0) +#define BISECT_SHOW_TRIED (1<<1) + +struct rev_list_info { + struct rev_info *revs; + int bisect_show_flags; + int show_timestamp; + int hdr_termination; + const char *header_prefix; +}; + +extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all); + +extern int bisect_next_all(const char *prefix); + +extern int estimate_bisect_steps(int all); + +#endif @@ -32,21 +32,59 @@ static int find_tracked_branch(struct remote *remote, void *priv) return 0; } -static int should_setup_rebase(const struct tracking *tracking) +static int should_setup_rebase(const char *origin) { switch (autorebase) { case AUTOREBASE_NEVER: return 0; case AUTOREBASE_LOCAL: - return tracking->remote == NULL; + return origin == NULL; case AUTOREBASE_REMOTE: - return tracking->remote != NULL; + return origin != NULL; case AUTOREBASE_ALWAYS: return 1; } return 0; } +void install_branch_config(int flag, const char *local, const char *origin, const char *remote) +{ + struct strbuf key = STRBUF_INIT; + int rebasing = should_setup_rebase(origin); + + strbuf_addf(&key, "branch.%s.remote", local); + git_config_set(key.buf, origin ? origin : "."); + + strbuf_reset(&key); + strbuf_addf(&key, "branch.%s.merge", local); + git_config_set(key.buf, remote); + + if (rebasing) { + strbuf_reset(&key); + strbuf_addf(&key, "branch.%s.rebase", local); + git_config_set(key.buf, "true"); + } + + if (flag & BRANCH_CONFIG_VERBOSE) { + strbuf_reset(&key); + + strbuf_addstr(&key, origin ? "remote" : "local"); + + /* Are we tracking a proper "branch"? */ + if (!prefixcmp(remote, "refs/heads/")) { + strbuf_addf(&key, " branch %s", remote + 11); + if (origin) + strbuf_addf(&key, " from %s", origin); + } + else + strbuf_addf(&key, " ref %s", remote); + printf("Branch %s set up to track %s%s.\n", + local, key.buf, + rebasing ? " by rebasing" : ""); + } + strbuf_release(&key); +} + /* * This is called when new_ref is branched off of orig_ref, and tries * to infer the settings for branch.<new_ref>.{remote,merge} from the @@ -55,7 +93,6 @@ static int should_setup_rebase(const struct tracking *tracking) static int setup_tracking(const char *new_ref, const char *orig_ref, enum branch_track track) { - char key[1024]; struct tracking tracking; if (strlen(new_ref) > 1024 - 7 - 7 - 1) @@ -80,19 +117,10 @@ static int setup_tracking(const char *new_ref, const char *orig_ref, return error("Not tracking: ambiguous information for ref %s", orig_ref); - sprintf(key, "branch.%s.remote", new_ref); - git_config_set(key, tracking.remote ? tracking.remote : "."); - sprintf(key, "branch.%s.merge", new_ref); - git_config_set(key, tracking.src ? tracking.src : orig_ref); - printf("Branch %s set up to track %s branch %s.\n", new_ref, - tracking.remote ? "remote" : "local", orig_ref); - if (should_setup_rebase(&tracking)) { - sprintf(key, "branch.%s.rebase", new_ref); - git_config_set(key, "true"); - printf("This branch will rebase on pull.\n"); - } - free(tracking.src); + install_branch_config(BRANCH_CONFIG_VERBOSE, new_ref, tracking.remote, + tracking.src ? tracking.src : orig_ref); + free(tracking.src); return 0; } @@ -106,16 +134,8 @@ void create_branch(const char *head, char *real_ref, msg[PATH_MAX + 20]; struct strbuf ref = STRBUF_INIT; int forcing = 0; - int len; - - 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)) + if (strbuf_check_branch_ref(&ref, name)) die("'%s' is not a valid branch name.", name); if (resolve_ref(ref.buf, sha1, 1, NULL)) { @@ -21,4 +21,11 @@ void create_branch(const char *head, const char *name, const char *start_name, */ void remove_branch_state(void); +/* + * Configure local branch "local" to merge remote branch "remote" + * taken from origin "origin". + */ +#define BRANCH_CONFIG_VERBOSE 01 +extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote); + #endif diff --git a/builtin-add.c b/builtin-add.c index 08443f2f1e..56e522127c 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -10,12 +10,14 @@ #include "cache-tree.h" #include "run-command.h" #include "parse-options.h" +#include "diff.h" +#include "revision.h" static const char * const builtin_add_usage[] = { "git add [options] [--] <filepattern>...", NULL }; -static int patch_interactive, add_interactive; +static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static void fill_pathspec_matches(const char **pathspec, char *seen, int specs) @@ -61,7 +63,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p fill_pathspec_matches(pathspec, seen, specs); for (i = 0; i < specs; i++) { - if (!seen[i] && !file_exists(pathspec[i])) + if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i])) die("pathspec '%s' did not match any files", pathspec[i]); } @@ -104,7 +106,7 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec, /* Set up the default git porcelain excludes */ memset(dir, 0, sizeof(*dir)); if (!ignored_too) { - dir->collect_ignored = 1; + dir->flags |= DIR_COLLECT_IGNORED; setup_standard_excludes(dir); } @@ -148,7 +150,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p if (pathspec) { const char **p; for (p = pathspec; *p; p++) { - if (has_symlink_leading_path(strlen(*p), *p)) { + if (has_symlink_leading_path(*p, strlen(*p))) { int len = prefix ? strlen(prefix) : 0; die("'%s' is beyond a symbolic link", *p + len); } @@ -187,6 +189,51 @@ int interactive_add(int argc, const char **argv, const char *prefix) return status; } +int edit_patch(int argc, const char **argv, const char *prefix) +{ + char *file = xstrdup(git_path("ADD_EDIT.patch")); + const char *apply_argv[] = { "apply", "--recount", "--cached", + file, NULL }; + struct child_process child; + struct rev_info rev; + int out; + struct stat st; + + git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ + + if (read_cache() < 0) + die ("Could not read the index"); + + init_revisions(&rev, prefix); + rev.diffopt.context = 7; + + argc = setup_revisions(argc, argv, &rev, NULL); + rev.diffopt.output_format = DIFF_FORMAT_PATCH; + out = open(file, O_CREAT | O_WRONLY, 0644); + if (out < 0) + die ("Could not open '%s' for writing.", file); + rev.diffopt.file = fdopen(out, "w"); + rev.diffopt.close_file = 1; + if (run_diff_files(&rev, 0)) + die ("Could not write patch"); + + launch_editor(file, NULL, NULL); + + if (stat(file, &st)) + die("Could not stat '%s'", file); + if (!st.st_size) + die("Empty patch. Aborted."); + + memset(&child, 0, sizeof(child)); + child.git_cmd = 1; + child.argv = apply_argv; + if (run_command(&child)) + die ("Could not apply '%s'", file); + + unlink(file); + return 0; +} + static struct lock_file lock_file; static const char ignore_error[] = @@ -201,6 +248,7 @@ static struct option builtin_add_options[] = { OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), + OPT_BOOLEAN('e', "edit", &edit_interactive, "edit current diff and apply"), OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), OPT_BOOLEAN('N', "intent-to-add", &intent_to_add, "record only the fact that the path will be added later"), @@ -250,14 +298,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) int add_new_files; int require_pathspec; - argc = parse_options(argc, argv, builtin_add_options, - builtin_add_usage, 0); + git_config(add_config, NULL); + + argc = parse_options(argc, argv, prefix, builtin_add_options, + builtin_add_usage, PARSE_OPT_KEEP_ARGV0); if (patch_interactive) add_interactive = 1; if (add_interactive) - exit(interactive_add(argc, argv, prefix)); + exit(interactive_add(argc - 1, argv + 1, prefix)); - git_config(add_config, NULL); + if (edit_interactive) + return(edit_patch(argc, argv, prefix)); + argc--; + argv++; if (addremove && take_worktree_changes) die("-A and -u are mutually incompatible"); diff --git a/builtin-apply.c b/builtin-apply.c index c6feaf5ca8..89a5185cea 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -320,6 +320,20 @@ static int name_terminate(const char *name, int namelen, int c, int terminate) return 1; } +/* remove double slashes to make --index work with such filenames */ +static char *squash_slash(char *name) +{ + int i = 0, j = 0; + + while (name[i]) { + if ((name[j++] = name[i++]) == '/') + while (name[i] == '/') + i++; + } + name[j] = '\0'; + return name; +} + static char *find_name(const char *line, char *def, int p_value, int terminate) { int len; @@ -349,7 +363,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) free(def); if (root) strbuf_insert(&name, 0, root, root_len); - return strbuf_detach(&name, NULL); + return squash_slash(strbuf_detach(&name, NULL)); } } strbuf_release(&name); @@ -369,10 +383,10 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) start = line; } if (!start) - return def; + return squash_slash(def); len = line - start; if (!len) - return def; + return squash_slash(def); /* * Generally we prefer the shorter name, especially @@ -383,7 +397,7 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) if (def) { int deflen = strlen(def); if (deflen < len && !strncmp(start, def, deflen)) - return def; + return squash_slash(def); free(def); } @@ -392,10 +406,10 @@ static char *find_name(const char *line, char *def, int p_value, int terminate) strcpy(ret, root); memcpy(ret + root_len, start, len); ret[root_len + len] = '\0'; - return ret; + return squash_slash(ret); } - return xmemdupz(start, len); + return squash_slash(xmemdupz(start, len)); } static int count_slashes(const char *cp) @@ -2394,7 +2408,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists) * In such a case, path "new_name" does not exist as * far as git is concerned. */ - if (has_symlink_leading_path(strlen(new_name), new_name)) + if (has_symlink_leading_path(new_name, strlen(new_name))) return 0; return error("%s: already exists in working directory", new_name); @@ -2487,7 +2501,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s if ((st_mode ^ patch->old_mode) & S_IFMT) return error("%s: wrong type", old_name); if (st_mode != patch->old_mode) - fprintf(stderr, "warning: %s has type %o, expected %o\n", + warning("%s has type %o, expected %o", old_name, st_mode, patch->old_mode); if (!patch->new_mode && !patch->is_delete) patch->new_mode = st_mode; @@ -2781,7 +2795,7 @@ static void remove_file(struct patch *patch, int rmdir_empty) if (rmdir(patch->old_name)) warning("unable to remove submodule %s", patch->old_name); - } else if (!unlink(patch->old_name) && rmdir_empty) { + } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) { remove_path(patch->old_name); } } @@ -2891,7 +2905,7 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned if (!try_create_file(newpath, mode, buf, size)) { if (!rename(newpath, path)) return; - unlink(newpath); + unlink_or_warn(newpath); break; } if (errno != EEXIST) @@ -2971,8 +2985,7 @@ static int write_out_one_reject(struct patch *patch) cnt = strlen(patch->new_name); if (ARRAY_SIZE(namebuf) <= cnt + 5) { cnt = ARRAY_SIZE(namebuf) - 5; - fprintf(stderr, - "warning: truncating .rej filename to %.*s.rej", + warning("truncating .rej filename to %.*s.rej", cnt - 1, patch->new_name); } memcpy(namebuf, patch->new_name, cnt); @@ -3263,10 +3276,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) "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"), + { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary, + NULL, "old option, now no-op", + PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, + { OPTION_BOOLEAN, 0, "binary", &binary, + NULL, "old option, now no-op", + PARSE_OPT_HIDDEN | PARSE_OPT_NOARG }, OPT_BOOLEAN(0, "numstat", &numstat, "shows number of added and deleted lines in decimal notation"), OPT_BOOLEAN(0, "summary", &summary, @@ -3279,7 +3294,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) "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", + OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor, "build a temporary index based on embedded index information"), { OPTION_CALLBACK, 'z', NULL, NULL, NULL, "paths are separated with NUL character", @@ -3314,8 +3329,9 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) if (apply_default_whitespace) parse_whitespace_option(apply_default_whitespace); - argc = parse_options(argc, argv, builtin_apply_options, + argc = parse_options(argc, argv, prefix, builtin_apply_options, apply_usage, 0); + if (apply_with_reject) apply = apply_verbosely = 1; if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor)) @@ -3354,8 +3370,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) squelch_whitespace_errors < whitespace_error) { int squelched = whitespace_error - squelch_whitespace_errors; - fprintf(stderr, "warning: squelched %d " - "whitespace error%s\n", + warning("squelched %d " + "whitespace error%s", squelched, squelched == 1 ? "" : "s"); } @@ -3365,12 +3381,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix) whitespace_error == 1 ? "" : "s", whitespace_error == 1 ? "s" : ""); if (applied_after_fixing_ws && apply) - fprintf(stderr, "warning: %d line%s applied after" - " fixing whitespace errors.\n", + warning("%d line%s applied after" + " fixing whitespace errors.", applied_after_fixing_ws, applied_after_fixing_ws == 1 ? "" : "s"); else if (whitespace_error) - fprintf(stderr, "warning: %d line%s add%s whitespace errors.\n", + warning("%d line%s add%s whitespace errors.", whitespace_error, whitespace_error == 1 ? "" : "s", whitespace_error == 1 ? "s" : ""); diff --git a/builtin-archive.c b/builtin-archive.c index 5ceec433fd..3c5a5a7822 100644 --- a/builtin-archive.c +++ b/builtin-archive.c @@ -5,44 +5,35 @@ #include "cache.h" #include "builtin.h" #include "archive.h" +#include "parse-options.h" #include "pkt-line.h" #include "sideband.h" -static int run_remote_archiver(const char *remote, int argc, - const char **argv) +static void create_output_file(const char *output_file) +{ + int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (output_fd < 0) + die("could not create archive file: %s ", output_file); + if (output_fd != 1) { + if (dup2(output_fd, 1) < 0) + die("could not redirect output"); + else + close(output_fd); + } +} + +static int run_remote_archiver(int argc, const char **argv, + const char *remote, const char *exec) { char *url, buf[LARGE_PACKET_MAX]; int fd[2], i, len, rv; struct child_process *conn; - const char *exec = "git-upload-archive"; - int exec_at = 0, exec_value_at = 0; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!prefixcmp(arg, "--exec=")) { - if (exec_at) - die("multiple --exec specified"); - exec = arg + 7; - exec_at = i; - } else if (!strcmp(arg, "--exec")) { - if (exec_at) - die("multiple --exec specified"); - if (i + 1 >= argc) - die("option --exec requires a value"); - exec = argv[i + 1]; - exec_at = i; - exec_value_at = ++i; - } - } url = xstrdup(remote); conn = git_connect(fd, url, exec, 0); - for (i = 1; i < argc; i++) { - if (i == exec_at || i == exec_value_at) - continue; + for (i = 1; i < argc; i++) packet_write(fd[1], "argument %s\n", argv[i]); - } packet_flush(fd[1]); len = packet_read_line(fd[0], buf, sizeof(buf)); @@ -61,7 +52,7 @@ static int run_remote_archiver(const char *remote, int argc, die("git archive: expected a flush"); /* Now, start reading from fd[0] and spit it out to stdout */ - rv = recv_sideband("archive", fd[0], 1, 2); + rv = recv_sideband("archive", fd[0], 1); close(fd[0]); close(fd[1]); rv |= finish_connect(conn); @@ -69,51 +60,34 @@ static int run_remote_archiver(const char *remote, int argc, return !!rv; } -static const char *extract_remote_arg(int *ac, const char **av) -{ - int ix, iy, cnt = *ac; - int no_more_options = 0; - const char *remote = NULL; - - for (ix = iy = 1; ix < cnt; ix++) { - const char *arg = av[ix]; - if (!strcmp(arg, "--")) - no_more_options = 1; - if (!no_more_options) { - if (!prefixcmp(arg, "--remote=")) { - if (remote) - die("Multiple --remote specified"); - remote = arg + 9; - continue; - } else if (!strcmp(arg, "--remote")) { - if (remote) - die("Multiple --remote specified"); - if (++ix >= cnt) - die("option --remote requires a value"); - remote = av[ix]; - continue; - } - if (arg[0] != '-') - no_more_options = 1; - } - if (ix != iy) - av[iy] = arg; - iy++; - } - if (remote) { - av[--cnt] = NULL; - *ac = cnt; - } - return remote; -} +#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH | \ + PARSE_OPT_KEEP_ARGV0 | \ + PARSE_OPT_KEEP_UNKNOWN | \ + PARSE_OPT_NO_INTERNAL_HELP ) int cmd_archive(int argc, const char **argv, const char *prefix) { + const char *exec = "git-upload-archive"; + const char *output = NULL; const char *remote = NULL; + struct option local_opts[] = { + OPT_STRING(0, "output", &output, "file", + "write the archive to this file"), + OPT_STRING(0, "remote", &remote, "repo", + "retrieve the archive from remote repository <repo>"), + OPT_STRING(0, "exec", &exec, "cmd", + "path to the remote git-upload-archive command"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, local_opts, NULL, + PARSE_OPT_KEEP_ALL); + + if (output) + create_output_file(output); - remote = extract_remote_arg(&argc, argv); if (remote) - return run_remote_archiver(remote, argc, argv); + return run_remote_archiver(argc, argv, remote, exec); setvbuf(stderr, NULL, _IOLBF, BUFSIZ); diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c new file mode 100644 index 0000000000..5b226399e1 --- /dev/null +++ b/builtin-bisect--helper.c @@ -0,0 +1,28 @@ +#include "builtin.h" +#include "cache.h" +#include "parse-options.h" +#include "bisect.h" + +static const char * const git_bisect_helper_usage[] = { + "git bisect--helper --next-all", + NULL +}; + +int cmd_bisect__helper(int argc, const char **argv, const char *prefix) +{ + int next_all = 0; + struct option options[] = { + OPT_BOOLEAN(0, "next-all", &next_all, + "perform 'git bisect next'"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + git_bisect_helper_usage, 0); + + if (!next_all) + usage_with_options(git_bisect_helper_usage, options); + + /* next-all */ + return bisect_next_all(prefix); +} diff --git a/builtin-blame.c b/builtin-blame.c index 1ead9b4830..0c2d29a430 100644 --- a/builtin-blame.c +++ b/builtin-blame.c @@ -1,5 +1,5 @@ /* - * Pickaxe + * Blame * * Copyright (c) 2006, Junio C Hamano */ @@ -40,6 +40,10 @@ static int reverse; static int blank_boundary; static int incremental; static int xdl_opts = XDF_NEED_MINIMAL; + +static enum date_mode blame_date_mode = DATE_ISO8601; +static size_t blame_date_width; + static struct string_list mailmap; #ifndef DEBUG @@ -74,6 +78,7 @@ static unsigned blame_copy_score; */ struct origin { int refcnt; + struct origin *previous; struct commit *commit; mmfile_t file; unsigned char blob_sha1[20]; @@ -115,6 +120,8 @@ static inline struct origin *origin_incref(struct origin *o) static void origin_decref(struct origin *o) { if (o && --o->refcnt <= 0) { + if (o->previous) + origin_decref(o->previous); free(o->file.ptr); free(o); } @@ -355,18 +362,28 @@ static struct origin *find_origin(struct scoreboard *sb, "", &diff_opts); diffcore_std(&diff_opts); - /* It is either one entry that says "modified", or "created", - * or nothing. - */ if (!diff_queued_diff.nr) { /* The path is the same as parent */ porigin = get_origin(sb, parent, origin->path); hashcpy(porigin->blob_sha1, origin->blob_sha1); - } - else if (diff_queued_diff.nr != 1) - die("internal error in blame::find_origin"); - else { - struct diff_filepair *p = diff_queued_diff.queue[0]; + } else { + /* + * Since origin->path is a pathspec, if the parent + * commit had it as a directory, we will see a whole + * bunch of deletion of files in the directory that we + * do not care about. + */ + int i; + struct diff_filepair *p = NULL; + for (i = 0; i < diff_queued_diff.nr; i++) { + const char *name; + p = diff_queued_diff.queue[i]; + name = p->one->path ? p->one->path : p->two->path; + if (!strcmp(name, origin->path)) + break; + } + if (!p) + die("internal error in blame::find_origin"); switch (p->status) { default: die("internal error in blame::find_origin (%c)", @@ -866,7 +883,7 @@ static void find_copy_in_blob(struct scoreboard *sb, * Prepare mmfile that contains only the lines in ent. */ cp = nth_line(sb, ent->lno); - file_o.ptr = (char*) cp; + file_o.ptr = (char *) cp; cnt = ent->num_lines; while (cnt && cp < sb->final_buf + sb->final_buf_size) { @@ -1198,6 +1215,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt) struct origin *porigin = sg_origin[i]; if (!porigin) continue; + if (!origin->previous) { + origin_incref(porigin); + origin->previous = porigin; + } if (pass_blame_to_parent(sb, origin, porigin)) goto finish; } @@ -1415,6 +1436,39 @@ static void write_filename_info(const char *path) } /* + * Porcelain/Incremental format wants to show a lot of details per + * commit. Instead of repeating this every line, emit it only once, + * the first time each commit appears in the output. + */ +static int emit_one_suspect_detail(struct origin *suspect) +{ + struct commit_info ci; + + if (suspect->commit->object.flags & METAINFO_SHOWN) + return 0; + + suspect->commit->object.flags |= METAINFO_SHOWN; + get_commit_info(suspect->commit, &ci, 1); + printf("author %s\n", ci.author); + printf("author-mail %s\n", ci.author_mail); + printf("author-time %lu\n", ci.author_time); + printf("author-tz %s\n", ci.author_tz); + printf("committer %s\n", ci.committer); + printf("committer-mail %s\n", ci.committer_mail); + printf("committer-time %lu\n", ci.committer_time); + printf("committer-tz %s\n", ci.committer_tz); + printf("summary %s\n", ci.summary); + if (suspect->commit->object.flags & UNINTERESTING) + printf("boundary\n"); + if (suspect->previous) { + struct origin *prev = suspect->previous; + printf("previous %s ", sha1_to_hex(prev->commit->object.sha1)); + write_name_quoted(prev->path, stdout, '\n'); + } + return 1; +} + +/* * The blame_entry is found to be guilty for the range. Mark it * as such, and show it in incremental output. */ @@ -1429,22 +1483,7 @@ static void found_guilty_entry(struct blame_entry *ent) printf("%s %d %d %d\n", sha1_to_hex(suspect->commit->object.sha1), ent->s_lno + 1, ent->lno + 1, ent->num_lines); - if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - struct commit_info ci; - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); - printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); - printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - printf("summary %s\n", ci.summary); - if (suspect->commit->object.flags & UNINTERESTING) - printf("boundary\n"); - } + emit_one_suspect_detail(suspect); write_filename_info(suspect->path); maybe_flush_or_die(stdout, "stdout"); } @@ -1507,24 +1546,20 @@ static const char *format_time(unsigned long time, const char *tz_str, int show_raw_time) { static char time_buf[128]; - time_t t = time; - int minutes, tz; - struct tm *tm; + const char *time_str; + int time_len; + int tz; if (show_raw_time) { sprintf(time_buf, "%lu %s", time, tz_str); - return time_buf; } - - tz = atoi(tz_str); - minutes = tz < 0 ? -tz : tz; - minutes = (minutes / 100)*60 + (minutes % 100); - minutes = tz < 0 ? -minutes : minutes; - t = time + minutes * 60; - tm = gmtime(&t); - - strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm); - strcat(time_buf, tz_str); + else { + tz = atoi(tz_str); + time_str = show_date(time, tz, blame_date_mode); + time_len = strlen(time_str); + memcpy(time_buf, time_str, time_len); + memset(time_buf + time_len, ' ', blame_date_width - time_len); + } return time_buf; } @@ -1551,24 +1586,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent) ent->s_lno + 1, ent->lno + 1, ent->num_lines); - if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - struct commit_info ci; - suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); - printf("author %s\n", ci.author); - printf("author-mail %s\n", ci.author_mail); - printf("author-time %lu\n", ci.author_time); - printf("author-tz %s\n", ci.author_tz); - printf("committer %s\n", ci.committer); - printf("committer-mail %s\n", ci.committer_mail); - printf("committer-time %lu\n", ci.committer_time); - printf("committer-tz %s\n", ci.committer_tz); - write_filename_info(suspect->path); - printf("summary %s\n", ci.summary); - if (suspect->commit->object.flags & UNINTERESTING) - printf("boundary\n"); - } - else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH) + if (emit_one_suspect_detail(suspect) || + (suspect->commit->object.flags & MORE_THAN_ONE_PATH)) write_filename_info(suspect->path); cp = nth_line(sb, ent->lno); @@ -1695,7 +1714,7 @@ static int prepare_lines(struct scoreboard *sb) while (len--) { if (bol) { sb->lineno = xrealloc(sb->lineno, - sizeof(int* ) * (num + 1)); + sizeof(int *) * (num + 1)); sb->lineno[num] = buf - sb->final_buf; bol = 0; } @@ -1705,7 +1724,7 @@ static int prepare_lines(struct scoreboard *sb) } } sb->lineno = xrealloc(sb->lineno, - sizeof(int* ) * (num + incomplete + 1)); + sizeof(int *) * (num + incomplete + 1)); sb->lineno[num + incomplete] = buf - sb->final_buf; sb->num_lines = num + incomplete; return sb->num_lines; @@ -1806,36 +1825,6 @@ static void sanity_check_refcnt(struct scoreboard *sb) baa = 1; } } - for (ent = sb->ent; ent; ent = ent->next) { - /* Mark the ones that haven't been checked */ - if (0 < ent->suspect->refcnt) - ent->suspect->refcnt = -ent->suspect->refcnt; - } - for (ent = sb->ent; ent; ent = ent->next) { - /* - * ... then pick each and see if they have the the - * correct refcnt. - */ - int found; - struct blame_entry *e; - struct origin *suspect = ent->suspect; - - if (0 < suspect->refcnt) - continue; - suspect->refcnt = -suspect->refcnt; /* Unmark */ - for (found = 0, e = sb->ent; e; e = e->next) { - if (e->suspect != suspect) - continue; - found++; - } - if (suspect->refcnt != found) { - fprintf(stderr, "%s in %s has refcnt %d, not %d\n", - ent->suspect->path, - sha1_to_hex(ent->suspect->commit->object.sha1), - ent->suspect->refcnt, found); - baa = 2; - } - } if (baa) { int opt = 0160; find_alignment(sb, &opt); @@ -1910,7 +1899,7 @@ static const char *parse_loc(const char *spec, return spec; /* it could be a regexp of form /.../ */ - for (term = (char*) spec + 1; *term && *term != '/'; term++) { + for (term = (char *) spec + 1; *term && *term != '/'; term++) { if (*term == '\\') term++; } @@ -1975,6 +1964,12 @@ static int git_blame_config(const char *var, const char *value, void *cb) blank_boundary = git_config_bool(var, value); return 0; } + if (!strcmp(var, "blame.date")) { + if (!value) + return config_error_nonbool(var); + blame_date_mode = parse_date_format(value); + return 0; + } return git_default_config(var, value, cb); } @@ -2239,10 +2234,12 @@ int cmd_blame(int argc, const char **argv, const char *prefix) git_config(git_blame_config, NULL); init_revisions(&revs, NULL); + revs.date_mode = blame_date_mode; + save_commit_buffer = 0; dashdash_pos = 0; - parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH | + parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, blame_opt_usage)) { @@ -2267,8 +2264,35 @@ parse_done: die("reading graft file %s failed: %s", revs_file, strerror(errno)); - if (cmd_is_annotate) + if (cmd_is_annotate) { output_option |= OUTPUT_ANNOTATE_COMPAT; + blame_date_mode = DATE_ISO8601; + } else { + blame_date_mode = revs.date_mode; + } + + /* The maximum width used to show the dates */ + switch (blame_date_mode) { + case DATE_RFC2822: + blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700"); + break; + case DATE_ISO8601: + blame_date_width = sizeof("2006-10-19 16:00:04 -0700"); + break; + case DATE_RAW: + blame_date_width = sizeof("1161298804 -0700"); + break; + case DATE_SHORT: + blame_date_width = sizeof("2006-10-19"); + break; + case DATE_RELATIVE: + /* "normal" is used as the fallback for "relative" */ + case DATE_LOCAL: + case DATE_NORMAL: + blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700"); + break; + } + blame_date_width -= 1; /* strip the null */ if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER)) opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE | diff --git a/builtin-branch.c b/builtin-branch.c index 07166b8856..5687d6042c 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -32,18 +32,18 @@ static unsigned char head_sha1[20]; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { - "\033[m", /* reset */ - "", /* PLAIN (normal) */ - "\033[31m", /* REMOTE (red) */ - "", /* LOCAL (normal) */ - "\033[32m", /* CURRENT (green) */ + GIT_COLOR_RESET, + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_RED, /* REMOTE */ + GIT_COLOR_NORMAL, /* LOCAL */ + GIT_COLOR_GREEN, /* CURRENT */ }; enum color_branch { - COLOR_BRANCH_RESET = 0, - COLOR_BRANCH_PLAIN = 1, - COLOR_BRANCH_REMOTE = 2, - COLOR_BRANCH_LOCAL = 3, - COLOR_BRANCH_CURRENT = 4, + BRANCH_COLOR_RESET = 0, + BRANCH_COLOR_PLAIN = 1, + BRANCH_COLOR_REMOTE = 2, + BRANCH_COLOR_LOCAL = 3, + BRANCH_COLOR_CURRENT = 4, }; static enum merge_filter { @@ -56,15 +56,15 @@ static unsigned char merge_filter_ref[20]; static int parse_branch_color_slot(const char *var, int ofs) { if (!strcasecmp(var+ofs, "plain")) - return COLOR_BRANCH_PLAIN; + return BRANCH_COLOR_PLAIN; if (!strcasecmp(var+ofs, "reset")) - return COLOR_BRANCH_RESET; + return BRANCH_COLOR_RESET; if (!strcasecmp(var+ofs, "remote")) - return COLOR_BRANCH_REMOTE; + return BRANCH_COLOR_REMOTE; if (!strcasecmp(var+ofs, "local")) - return COLOR_BRANCH_LOCAL; + return BRANCH_COLOR_LOCAL; if (!strcasecmp(var+ofs, "current")) - return COLOR_BRANCH_CURRENT; + return BRANCH_COLOR_CURRENT; die("bad config variable '%s'", var); } @@ -121,11 +121,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) die("Couldn't look up commit object for HEAD"); } 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); - + strbuf_branchname(&bname, argv[i]); if (kinds == REF_LOCAL_BRANCH && !strcmp(head, bname.buf)) { error("Cannot delete the branch '%s' " "which you are currently on.", bname.buf); @@ -188,7 +184,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds) struct ref_item { char *name; - unsigned int kind; + char *dest; + unsigned int kind, len; struct commit *commit; }; @@ -200,22 +197,47 @@ struct ref_list { int kinds; }; +static char *resolve_symref(const char *src, const char *prefix) +{ + unsigned char sha1[20]; + int flag; + const char *dst, *cp; + + dst = resolve_ref(src, sha1, 0, &flag); + if (!(dst && (flag & REF_ISSYMREF))) + return NULL; + if (prefix && (cp = skip_prefix(dst, prefix))) + dst = cp; + return xstrdup(dst); +} + 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); struct ref_item *newitem; struct commit *commit; - int kind; - int len; + int kind, i; + const char *prefix, *orig_refname = refname; + + static struct { + int kind; + const char *prefix; + int pfxlen; + } ref_kind[] = { + { REF_LOCAL_BRANCH, "refs/heads/", 11 }, + { REF_REMOTE_BRANCH, "refs/remotes/", 13 }, + }; /* Detect kind */ - if (!prefixcmp(refname, "refs/heads/")) { - kind = REF_LOCAL_BRANCH; - refname += 11; - } else if (!prefixcmp(refname, "refs/remotes/")) { - kind = REF_REMOTE_BRANCH; - refname += 13; - } else + for (i = 0; i < ARRAY_SIZE(ref_kind); i++) { + prefix = ref_kind[i].prefix; + if (strncmp(refname, prefix, ref_kind[i].pfxlen)) + continue; + kind = ref_kind[i].kind; + refname += ref_kind[i].pfxlen; + break; + } + if (ARRAY_SIZE(ref_kind) <= i) return 0; commit = lookup_commit_reference_gently(sha1, 1); @@ -246,9 +268,14 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem->name = xstrdup(refname); newitem->kind = kind; newitem->commit = commit; - len = strlen(newitem->name); - if (len > ref_list->maxwidth) - ref_list->maxwidth = len; + newitem->len = strlen(refname); + newitem->dest = resolve_symref(orig_refname, prefix); + /* adjust for "remotes/" */ + if (newitem->kind == REF_REMOTE_BRANCH && + ref_list->kinds != REF_REMOTE_BRANCH) + newitem->len += 8; + if (newitem->len > ref_list->maxwidth) + ref_list->maxwidth = newitem->len; return 0; } @@ -257,8 +284,10 @@ static void free_ref_list(struct ref_list *ref_list) { int i; - for (i = 0; i < ref_list->index; i++) + for (i = 0; i < ref_list->index; i++) { free(ref_list->list[i].name); + free(ref_list->list[i].dest); + } free(ref_list->list); } @@ -272,19 +301,30 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } -static void fill_tracking_info(struct strbuf *stat, const char *branch_name) +static void fill_tracking_info(struct strbuf *stat, const char *branch_name, + int show_upstream_ref) { int ours, theirs; struct branch *branch = branch_get(branch_name); - if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs)) + if (!stat_tracking_info(branch, &ours, &theirs)) { + if (branch && branch->merge && branch->merge[0]->dst && + show_upstream_ref) + strbuf_addf(stat, "[%s] ", + shorten_unambiguous_ref(branch->merge[0]->dst, 0)); return; + } + + strbuf_addch(stat, '['); + if (show_upstream_ref) + strbuf_addf(stat, "%s: ", + shorten_unambiguous_ref(branch->merge[0]->dst, 0)); if (!ours) - strbuf_addf(stat, "[behind %d] ", theirs); + strbuf_addf(stat, "behind %d] ", theirs); else if (!theirs) - strbuf_addf(stat, "[ahead %d] ", ours); + strbuf_addf(stat, "ahead %d] ", ours); else - strbuf_addf(stat, "[ahead %d, behind %d] ", ours, theirs); + strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs); } static int matches_merge_filter(struct commit *commit) @@ -299,34 +339,46 @@ static int matches_merge_filter(struct commit *commit) } static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, - int abbrev, int current) + int abbrev, int current, char *prefix) { char c; int color; struct commit *commit = item->commit; + struct strbuf out = STRBUF_INIT, name = STRBUF_INIT; if (!matches_merge_filter(commit)) return; switch (item->kind) { case REF_LOCAL_BRANCH: - color = COLOR_BRANCH_LOCAL; + color = BRANCH_COLOR_LOCAL; break; case REF_REMOTE_BRANCH: - color = COLOR_BRANCH_REMOTE; + color = BRANCH_COLOR_REMOTE; break; default: - color = COLOR_BRANCH_PLAIN; + color = BRANCH_COLOR_PLAIN; break; } c = ' '; if (current) { c = '*'; - color = COLOR_BRANCH_CURRENT; + color = BRANCH_COLOR_CURRENT; } - if (verbose) { + strbuf_addf(&name, "%s%s", prefix, item->name); + if (verbose) + strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color), + maxwidth, name.buf, + branch_get_color(BRANCH_COLOR_RESET)); + else + strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color), + name.buf, branch_get_color(BRANCH_COLOR_RESET)); + + if (item->dest) + strbuf_addf(&out, " -> %s", item->dest); + else if (verbose) { struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT; const char *sub = " **** invalid ref ****"; @@ -338,30 +390,27 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose, } if (item->kind == REF_LOCAL_BRANCH) - fill_tracking_info(&stat, item->name); + fill_tracking_info(&stat, item->name, verbose > 1); - printf("%c %s%-*s%s %s %s%s\n", c, branch_get_color(color), - maxwidth, item->name, - branch_get_color(COLOR_BRANCH_RESET), - find_unique_abbrev(item->commit->object.sha1, abbrev), - stat.buf, sub); + strbuf_addf(&out, " %s %s%s", + find_unique_abbrev(item->commit->object.sha1, abbrev), + stat.buf, sub); strbuf_release(&stat); strbuf_release(&subject); - } else { - printf("%c %s%s%s\n", c, branch_get_color(color), item->name, - branch_get_color(COLOR_BRANCH_RESET)); } + printf("%s\n", out.buf); + strbuf_release(&name); + strbuf_release(&out); } static int calc_maxwidth(struct ref_list *refs) { - int i, l, w = 0; + int i, w = 0; for (i = 0; i < refs->index; i++) { if (!matches_merge_filter(refs->list[i].commit)) continue; - l = strlen(refs->list[i].name); - if (l > w) - w = l; + if (refs->list[i].len > w) + w = refs->list[i].len; } return w; } @@ -397,11 +446,13 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str is_descendant_of(head_commit, with_commit)) { struct ref_item item; item.name = xstrdup("(no branch)"); + item.len = strlen(item.name); item.kind = REF_LOCAL_BRANCH; + item.dest = NULL; item.commit = head_commit; - if (strlen(item.name) > ref_list.maxwidth) - ref_list.maxwidth = strlen(item.name); - print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1); + if (item.len > ref_list.maxwidth) + ref_list.maxwidth = item.len; + print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1, ""); free(item.name); } @@ -409,8 +460,11 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str int current = !detached && (ref_list.list[i].kind == REF_LOCAL_BRANCH) && !strcmp(ref_list.list[i].name, head); + char *prefix = (kinds != REF_REMOTE_BRANCH && + ref_list.list[i].kind == REF_REMOTE_BRANCH) + ? "remotes/" : ""; print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose, - abbrev, current); + abbrev, current, prefix); } free_ref_list(&ref_list); @@ -421,22 +475,27 @@ static void rename_branch(const char *oldname, const char *newname, int force) struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; unsigned char sha1[20]; struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; + int recovery = 0; if (!oldname) die("cannot rename the current branch while not on any."); - strbuf_addf(&oldref, "refs/heads/%s", oldname); - - if (check_ref_format(oldref.buf)) - die("Invalid branch name: %s", oldref.buf); - - strbuf_addf(&newref, "refs/heads/%s", newname); + if (strbuf_check_branch_ref(&oldref, oldname)) { + /* + * Bad name --- this could be an attempt to rename a + * ref that we used to allow to be created by accident. + */ + if (resolve_ref(oldref.buf, sha1, 1, NULL)) + recovery = 1; + else + die("Invalid branch name: '%s'", oldname); + } - if (check_ref_format(newref.buf)) - die("Invalid branch name: %s", newref.buf); + if (strbuf_check_branch_ref(&newref, newname)) + die("Invalid branch name: '%s'", newname); if (resolve_ref(newref.buf, sha1, 1, NULL) && !force) - die("A branch named '%s' already exists.", newname); + die("A branch named '%s' already exists.", newref.buf + 11); strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); @@ -445,6 +504,9 @@ static void rename_branch(const char *oldname, const char *newname, int force) die("Branch rename failed"); strbuf_release(&logmsg); + if (recovery) + warning("Renamed a misnamed branch '%s' away", oldref.buf + 11); + /* no need to pass logmsg here as HEAD didn't really move */ if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL)) die("Branch renamed to %s, but HEAD is not updated!", newname); @@ -485,7 +547,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_GROUP("Generic options"), OPT__VERBOSE(&verbose), - OPT_SET_INT( 0 , "track", &track, "set up tracking mode (see git-pull(1))", + OPT_SET_INT('t', "track", &track, "set up tracking mode (see git-pull(1))", BRANCH_TRACK_EXPLICIT), OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"), OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches", @@ -548,7 +610,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) } hashcpy(merge_filter_ref, head_sha1); - argc = parse_options(argc, argv, options, builtin_branch_usage, 0); + argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, + 0); if (!!delete + !!rename + !!force_create > 1) usage_with_options(builtin_branch_usage, options); diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 8fad19daed..5906842008 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -201,8 +201,8 @@ static int batch_objects(int print_contents) } static const char * const cat_file_usage[] = { - "git cat-file [-t|-s|-e|-p|<type>] <sha1>", - "git cat-file [--batch|--batch-check] < <list_of_sha1s>", + "git cat-file (-t|-s|-e|-p|<type>) <object>", + "git cat-file (--batch|--batch-check) < <list_of_objects>", NULL }; @@ -231,7 +231,7 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) if (argc != 3 && argc != 2) usage_with_options(cat_file_usage, options); - argc = parse_options(argc, argv, options, cat_file_usage, 0); + argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); if (opt) { if (argc == 1) diff --git a/builtin-check-attr.c b/builtin-check-attr.c index 15a04b7179..8bd0430098 100644 --- a/builtin-check-attr.c +++ b/builtin-check-attr.c @@ -69,8 +69,8 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix) int cnt, i, doubledash; const char *errstr = NULL; - argc = parse_options(argc, argv, check_attr_options, check_attr_usage, - PARSE_OPT_KEEP_DASHDASH); + argc = parse_options(argc, argv, prefix, check_attr_options, + check_attr_usage, PARSE_OPT_KEEP_DASHDASH); if (!argc) usage_with_options(check_attr_usage, check_attr_options); diff --git a/builtin-check-ref-format.c b/builtin-check-ref-format.c index 701de439ae..f9381e07ea 100644 --- a/builtin-check-ref-format.c +++ b/builtin-check-ref-format.c @@ -5,9 +5,18 @@ #include "cache.h" #include "refs.h" #include "builtin.h" +#include "strbuf.h" int cmd_check_ref_format(int argc, const char **argv, const char *prefix) { + if (argc == 3 && !strcmp(argv[1], "--branch")) { + struct strbuf sb = STRBUF_INIT; + + if (strbuf_check_branch_ref(&sb, argv[2])) + die("'%s' is not a valid branch name", argv[2]); + printf("%s\n", sb.buf + 11); + exit(0); + } if (argc != 2) usage("git check-ref-format refname"); return !!check_ref_format(argv[1]); diff --git a/builtin-checkout-index.c b/builtin-checkout-index.c index 0d534bc023..a7a5ee10f3 100644 --- a/builtin-checkout-index.c +++ b/builtin-checkout-index.c @@ -124,7 +124,7 @@ static int checkout_file(const char *name, int prefix_length) static void checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; - struct cache_entry* last_ce = NULL; + struct cache_entry *last_ce = NULL; for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; @@ -249,7 +249,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) die("invalid cache"); } - argc = parse_options(argc, argv, builtin_checkout_index_options, + argc = parse_options(argc, argv, prefix, builtin_checkout_index_options, builtin_checkout_index_usage, 0); state.force = force; state.quiet = quiet; @@ -278,7 +278,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) p = prefix_path(prefix, prefix_length, arg); checkout_file(p, prefix_length); if (p < arg || p > arg + strlen(arg)) - free((char*)p); + free((char *)p); } if (read_from_stdin) { diff --git a/builtin-checkout.c b/builtin-checkout.c index ffdb33aef5..8a9a474218 100644 --- a/builtin-checkout.c +++ b/builtin-checkout.c @@ -179,7 +179,7 @@ static int checkout_merged(int pos, struct checkout *state) /* * NEEDSWORK: * There is absolutely no reason to write this as a blob object - * and create a phoney cache entry just to leak. This hack is + * and create a phony cache entry just to leak. This hack is * primarily to get to the write_entry() machinery that massages * the contents to work-tree format and writes out which only * allows it for a cache entry. The code in write_entry() needs @@ -216,7 +216,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec, struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); newfd = hold_locked_index(lock_file, 1); - if (read_cache() < 0) + if (read_cache_preload(pathspec) < 0) return error("corrupt index file"); if (source_tree) @@ -293,6 +293,8 @@ static void show_local_changes(struct object *head) init_revisions(&rev, NULL); rev.abbrev = 0; rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS; + if (diff_setup_done(&rev.diffopt) < 0) + die("diff_setup_done failed"); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); } @@ -349,16 +351,11 @@ struct branch_info { static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; - int ret; - if ((ret = interpret_nth_last_branch(branch->name, &buf)) - && ret == strlen(branch->name)) { + strbuf_branchname(&buf, branch->name); + if (strcmp(buf.buf, 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); - } + strbuf_splice(&buf, 0, 0, "refs/heads/", 11); branch->path = strbuf_detach(&buf, NULL); } @@ -369,7 +366,7 @@ static int merge_working_tree(struct checkout_opts *opts, struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); int newfd = hold_locked_index(lock_file, 1); - if (read_cache() < 0) + if (read_cache_preload(NULL) < 0) return error("corrupt index file"); if (opts->force) { @@ -403,7 +400,7 @@ static int merge_working_tree(struct checkout_opts *opts, topts.verbose_update = !opts->quiet; topts.fn = twoway_merge; topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->show_ignored = 1; + topts.dir->flags |= DIR_SHOW_IGNORED; topts.dir->exclude_per_dir = ".gitignore"; tree = parse_tree_indirect(old->commit->object.sha1); init_tree_desc(&trees[0], tree->buffer, tree->size); @@ -544,18 +541,10 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) parse_commit(new->commit); } - /* - * If we were on a detached HEAD, but we are now moving to - * a new commit, we want to mention the old commit once more - * to remind the user that it might be lost. - */ - if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) - describe_detached_head("Previous HEAD position was", old.commit); - if (!old.commit && !opts->force) { if (!opts->quiet) { - fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n"); - fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name); + warning("You appear to be on a branch yet to be born."); + warning("Forcing checkout of %s.", new->name); } opts->force = 1; } @@ -564,6 +553,14 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new) if (ret) return ret; + /* + * If we were on a detached HEAD, but have now moved to + * a new commit, we want to mention the old commit once more + * to remind the user that it might be lost. + */ + if (!opts->quiet && !old.path && old.commit && new->commit != old.commit) + describe_detached_head("Previous HEAD position was", old.commit); + update_refs_for_switch(opts, &old, new); ret = post_checkout_hook(old.commit, new->commit, 1); @@ -608,7 +605,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.track = BRANCH_TRACK_UNSPECIFIED; - argc = parse_options(argc, argv, options, checkout_usage, + argc = parse_options(argc, argv, prefix, options, checkout_usage, PARSE_OPT_KEEP_DASHDASH); /* --track without -b should DWIM */ @@ -734,12 +731,11 @@ no_reference: if (opts.new_branch) { struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, "refs/heads/"); - strbuf_addstr(&buf, opts.new_branch); + if (strbuf_check_branch_ref(&buf, opts.new_branch)) + die("git checkout: we do not like '%s' as a branch name.", + opts.new_branch); if (!get_sha1(buf.buf, rev)) die("git checkout: branch %s already exists", opts.new_branch); - if (check_ref_format(buf.buf)) - die("git checkout: we do not like '%s' as a branch name.", opts.new_branch); strbuf_release(&buf); } diff --git a/builtin-clean.c b/builtin-clean.c index f78c2fb108..1c1b6d26e9 100644 --- a/builtin-clean.c +++ b/builtin-clean.c @@ -56,11 +56,12 @@ int cmd_clean(int argc, const char **argv, const char *prefix) else config_set = 1; - argc = parse_options(argc, argv, options, builtin_clean_usage, 0); + argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, + 0); memset(&dir, 0, sizeof(dir)); if (ignored_only) - dir.show_ignored = 1; + dir.flags |= DIR_SHOW_IGNORED; if (ignored && ignored_only) die("-x and -X cannot be used together"); @@ -69,7 +70,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) die("clean.requireForce%s set and -n or -f not given; " "refusing to clean", config_set ? "" : " not"); - dir.show_other_directories = 1; + dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; if (!ignored) setup_standard_excludes(&dir); diff --git a/builtin-clone.c b/builtin-clone.c index c338910b1c..5c46496a43 100644 --- a/builtin-clone.c +++ b/builtin-clone.c @@ -20,6 +20,9 @@ #include "dir.h" #include "pack-refs.h" #include "sigchain.h" +#include "branch.h" +#include "remote.h" +#include "run-command.h" /* * Overall FIXMEs: @@ -101,11 +104,12 @@ static char *get_repo_path(const char *repo, int *is_bundle) static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) { const char *end = repo + strlen(repo), *start; + char *dir; /* - * Strip trailing slashes and /.git + * Strip trailing spaces, slashes and /.git */ - while (repo < end && is_dir_sep(end[-1])) + while (repo < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) end--; if (end - repo > 5 && is_dir_sep(end[-5]) && !strncmp(end - 4, ".git", 4)) { @@ -137,10 +141,33 @@ static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) if (is_bare) { struct strbuf result = STRBUF_INIT; strbuf_addf(&result, "%.*s.git", (int)(end - start), start); - return strbuf_detach(&result, 0); + dir = strbuf_detach(&result, 0); + } else + dir = xstrndup(start, end - start); + /* + * Replace sequences of 'control' characters and whitespace + * with one ascii space, remove leading and trailing spaces. + */ + if (*dir) { + char *out = dir; + int prev_space = 1 /* strip leading whitespace */; + for (end = dir; *end; ++end) { + char ch = *end; + if ((unsigned char)ch < '\x20') + ch = '\x20'; + if (isspace(ch)) { + if (prev_space) + continue; + prev_space = 1; + } else + prev_space = 0; + *out++ = ch; + } + *out = '\0'; + if (out > dir && prev_space) + out[-1] = '\0'; } - - return xstrndup(start, end - start); + return dir; } static void strip_trailing_slashes(char *dir) @@ -225,7 +252,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) } if (unlink(dest->buf) && errno != ENOENT) - die("failed to unlink %s", dest->buf); + die("failed to unlink %s: %s", + dest->buf, strerror(errno)); if (!option_no_hardlinks) { if (!link(src->buf, dest->buf)) continue; @@ -267,7 +295,7 @@ static const struct ref *clone_local(const char *src_repo, static const char *junk_work_tree; static const char *junk_git_dir; -pid_t junk_pid; +static pid_t junk_pid; static void remove_junk(void) { @@ -293,43 +321,6 @@ static void remove_junk_on_signal(int signo) raise(signo); } -static const struct ref *locate_head(const struct ref *refs, - const struct ref *mapped_refs, - const struct ref **remote_head_p) -{ - const struct ref *remote_head = NULL; - const struct ref *remote_master = NULL; - const struct ref *r; - for (r = refs; r; r = r->next) - if (!strcmp(r->name, "HEAD")) - remote_head = r; - - for (r = mapped_refs; r; r = r->next) - if (!strcmp(r->name, "refs/heads/master")) - remote_master = r; - - if (remote_head_p) - *remote_head_p = remote_head; - - /* If there's no HEAD value at all, never mind. */ - if (!remote_head) - return NULL; - - /* If refs/heads/master could be right, it is. */ - if (remote_master && !hashcmp(remote_master->old_sha1, - remote_head->old_sha1)) - return remote_master; - - /* Look for another ref that points there */ - for (r = mapped_refs; r; r = r->next) - if (r != remote_head && - !hashcmp(r->old_sha1, remote_head->old_sha1)) - return r; - - /* Nothing is the same */ - return NULL; -} - static struct ref *write_remote_refs(const struct ref *refs, struct refspec *refspec, const char *reflog) { @@ -350,23 +341,8 @@ 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; - int use_separate_remote = 1; int is_bundle = 0; struct stat buf; const char *repo_name, *repo, *work_tree, *git_dir; @@ -377,20 +353,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; char *src_ref_prefix = "refs/heads/"; + int err = 0; - struct refspec refspec; + struct refspec *refspec; + const char *fetch_pattern; junk_pid = getpid(); - argc = parse_options(argc, argv, builtin_clone_options, + argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); if (argc == 0) die("You must specify a repository to clone."); - if (option_no_hardlinks) - use_local_hardlinks = 0; - if (option_mirror) option_bare = 1; @@ -399,7 +374,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) die("--bare and --origin %s options are incompatible.", option_origin); option_no_checkout = 1; - use_separate_remote = 0; } if (!option_origin) @@ -457,7 +431,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) atexit(remove_junk); sigchain_push_common(remove_junk_on_signal); - setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1); + setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1); if (safe_create_leading_directories_const(git_dir) < 0) die("could not create leading directories of '%s'", git_dir); @@ -487,8 +461,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); } + strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); + if (option_mirror || !option_bare) { /* Configure the remote */ + strbuf_addf(&key, "remote.%s.fetch", option_origin); + git_config_set_multivar(key.buf, value.buf, "^$", 0); + strbuf_reset(&key); + if (option_mirror) { strbuf_addf(&key, "remote.%s.mirror", option_origin); git_config_set(key.buf, "true"); @@ -497,19 +477,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_addf(&key, "remote.%s.url", option_origin); git_config_set(key.buf, repo); - strbuf_reset(&key); - - strbuf_addf(&key, "remote.%s.fetch", option_origin); - strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf); - git_config_set_multivar(key.buf, value.buf, "^$", 0); strbuf_reset(&key); - strbuf_reset(&value); } - refspec.force = 0; - refspec.pattern = 1; - refspec.src = src_ref_prefix; - refspec.dst = branch_top.buf; + fetch_pattern = value.buf; + refspec = parse_fetch_refspec(1, &fetch_pattern); + + strbuf_reset(&value); if (path && !is_bundle) refs = clone_local(path, git_dir); @@ -543,9 +517,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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); + remote_head = find_ref_by_name(refs, "HEAD"); + head_points_at = guess_remote_head(remote_head, mapped_refs, 0); } else { warning("You appear to have cloned an empty repository."); @@ -553,7 +528,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head = NULL; option_no_checkout = 1; if (!option_bare) - install_branch_config("master", option_origin, + install_branch_config(0, "master", option_origin, "refs/heads/master"); } @@ -583,7 +558,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) head_points_at->peer_ref->name, reflog_msg.buf); - install_branch_config(head, option_origin, + install_branch_config(0, head, option_origin, head_points_at->name); } } else if (remote_head) { @@ -631,6 +606,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (write_cache(fd, active_cache, active_nr) || commit_locked_index(lock_file)) die("unable to write new index file"); + + err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), + sha1_to_hex(remote_head->old_sha1), "1", NULL); } strbuf_release(&reflog_msg); @@ -638,5 +616,5 @@ int cmd_clone(int argc, const char **argv, const char *prefix) strbuf_release(&key); strbuf_release(&value); junk_pid = 0; - return 0; + return err; } diff --git a/builtin-commit.c b/builtin-commit.c index 81371b1d26..41e222d267 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -88,13 +88,13 @@ static struct option builtin_commit_options[] = { OPT__VERBOSE(&verbose), OPT_GROUP("Commit message options"), - OPT_STRING('F', "file", &logfile, "FILE", "read log from file"), + OPT_FILENAME('F', "file", &logfile, "read log from file"), OPT_STRING(0, "author", &force_author, "AUTHOR", "override author for commit"), OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit "), OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"), OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"), - OPT_STRING('t', "template", &template_file, "FILE", "use specified template file"), + OPT_FILENAME('t', "template", &template_file, "use specified template file"), OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"), OPT_GROUP("Commit contents options"), @@ -697,9 +697,8 @@ static int parse_and_validate_options(int argc, const char *argv[], { int f = 0; - argc = parse_options(argc, argv, builtin_commit_options, usage, 0); - logfile = parse_options_fix_filename(prefix, logfile); - template_file = parse_options_fix_filename(prefix, template_file); + argc = parse_options(argc, argv, prefix, builtin_commit_options, usage, + 0); if (force_author && !strchr(force_author, '>')) force_author = find_author_by_nickname(force_author); diff --git a/builtin-config.c b/builtin-config.c index f71016204b..60915f91ca 100644 --- a/builtin-config.c +++ b/builtin-config.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "color.h" +#include "parse-options.h" -static const char git_config_set_usage[] = -"git config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]"; +static const char *const builtin_config_usage[] = { + "git config [options]", + NULL +}; static char *key; static regex_t *key_regexp; @@ -16,7 +19,67 @@ static int seen; static char delim = '='; static char key_delim = ' '; static char term = '\n'; -static enum { T_RAW, T_INT, T_BOOL, T_BOOL_OR_INT } type = T_RAW; + +static int use_global_config, use_system_config; +static const char *given_config_file; +static int actions, types; +static const char *get_color_slot, *get_colorbool_slot; +static int end_null; + +#define ACTION_GET (1<<0) +#define ACTION_GET_ALL (1<<1) +#define ACTION_GET_REGEXP (1<<2) +#define ACTION_REPLACE_ALL (1<<3) +#define ACTION_ADD (1<<4) +#define ACTION_UNSET (1<<5) +#define ACTION_UNSET_ALL (1<<6) +#define ACTION_RENAME_SECTION (1<<7) +#define ACTION_REMOVE_SECTION (1<<8) +#define ACTION_LIST (1<<9) +#define ACTION_EDIT (1<<10) +#define ACTION_SET (1<<11) +#define ACTION_SET_ALL (1<<12) +#define ACTION_GET_COLOR (1<<13) +#define ACTION_GET_COLORBOOL (1<<14) + +#define TYPE_BOOL (1<<0) +#define TYPE_INT (1<<1) +#define TYPE_BOOL_OR_INT (1<<2) + +static struct option builtin_config_options[] = { + OPT_GROUP("Config file location"), + OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"), + OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"), + OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"), + OPT_GROUP("Action"), + OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET), + OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL), + OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP), + OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL), + OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD), + OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET), + OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL), + OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION), + OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION), + OPT_BIT('l', "list", &actions, "list all", ACTION_LIST), + OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT), + OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"), + OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"), + OPT_GROUP("Type"), + OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL), + OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT), + OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT), + OPT_GROUP("Other"), + OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"), + OPT_END(), +}; + +static void check_argc(int argc, int min, int max) { + if (argc >= min && argc <= max) + return; + error("wrong number of arguments"); + usage_with_options(builtin_config_usage, builtin_config_options); +} static int show_all_config(const char *key_, const char *value_, void *cb) { @@ -27,7 +90,7 @@ static int show_all_config(const char *key_, const char *value_, void *cb) return 0; } -static int show_config(const char* key_, const char* value_, void *cb) +static int show_config(const char *key_, const char *value_, void *cb) { char value[256]; const char *vptr = value; @@ -49,11 +112,11 @@ static int show_config(const char* key_, const char* value_, void *cb) } if (seen && !do_all) dup_error = 1; - if (type == T_INT) + if (types == TYPE_INT) sprintf(value, "%d", git_config_int(key_, value_?value_:"")); - else if (type == T_BOOL) + else if (types == TYPE_BOOL) vptr = git_config_bool(key_, value_) ? "true" : "false"; - else if (type == T_BOOL_OR_INT) { + else if (types == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key_, value_, &is_bool); if (is_bool) @@ -74,7 +137,7 @@ static int show_config(const char* key_, const char* value_, void *cb) return 0; } -static int get_value(const char* key_, const char* regex_) +static int get_value(const char *key_, const char *regex_) { int ret = -1; char *tl; @@ -152,18 +215,18 @@ static char *normalize_value(const char *key, const char *value) if (!value) return NULL; - if (type == T_RAW) + if (types == 0) normalized = xstrdup(value); else { normalized = xmalloc(64); - if (type == T_INT) { + if (types == TYPE_INT) { int v = git_config_int(key, value); sprintf(normalized, "%d", v); } - else if (type == T_BOOL) + else if (types == TYPE_BOOL) sprintf(normalized, "%s", git_config_bool(key, value) ? "true" : "false"); - else if (type == T_BOOL_OR_INT) { + else if (types == TYPE_BOOL_OR_INT) { int is_bool, v; v = git_config_bool_or_int(key, value, &is_bool); if (!is_bool) @@ -178,6 +241,7 @@ static char *normalize_value(const char *key, const char *value) static int get_color_found; static const char *get_color_slot; +static const char *get_colorbool_slot; static char parsed_color[COLOR_MAXLEN]; static int git_get_color_config(const char *var, const char *value, void *cb) @@ -191,29 +255,8 @@ static int git_get_color_config(const char *var, const char *value, void *cb) return 0; } -static int get_color(int argc, const char **argv) +static void get_color(const char *def_color) { - /* - * grab the color setting for the given slot from the configuration, - * or parse the default value if missing, and return ANSI color - * escape sequence. - * - * e.g. - * git config --get-color color.diff.whitespace "blue reverse" - */ - const char *def_color = NULL; - - switch (argc) { - default: - usage(git_config_set_usage); - case 2: - def_color = argv[1]; - /* fallthru */ - case 1: - get_color_slot = argv[0]; - break; - } - get_color_found = 0; parsed_color[0] = '\0'; git_config(git_get_color_config, NULL); @@ -222,7 +265,6 @@ static int get_color(int argc, const char **argv) color_parse(def_color, "command line", parsed_color); fputs(parsed_color, stdout); - return 0; } static int stdout_is_tty; @@ -231,7 +273,7 @@ static int get_diff_color_found; static int git_get_colorbool_config(const char *var, const char *value, void *cb) { - if (!strcmp(var, get_color_slot)) { + if (!strcmp(var, get_colorbool_slot)) { get_colorbool_found = git_config_colorbool(var, value, stdout_is_tty); } @@ -246,183 +288,191 @@ static int git_get_colorbool_config(const char *var, const char *value, return 0; } -static int get_colorbool(int argc, const char **argv) +static int get_colorbool(int print) { - /* - * git config --get-colorbool <slot> [<stdout-is-tty>] - * - * returns "true" or "false" depending on how <slot> - * is configured. - */ - - if (argc == 2) - stdout_is_tty = git_config_bool("command line", argv[1]); - else if (argc == 1) - stdout_is_tty = isatty(1); - else - usage(git_config_set_usage); get_colorbool_found = -1; get_diff_color_found = -1; - get_color_slot = argv[0]; git_config(git_get_colorbool_config, NULL); if (get_colorbool_found < 0) { - if (!strcmp(get_color_slot, "color.diff")) + if (!strcmp(get_colorbool_slot, "color.diff")) get_colorbool_found = get_diff_color_found; if (get_colorbool_found < 0) get_colorbool_found = git_use_color_default; } - if (argc == 1) { - return get_colorbool_found ? 0 : 1; - } else { + if (print) { printf("%s\n", get_colorbool_found ? "true" : "false"); return 0; - } + } else + return get_colorbool_found ? 0 : 1; } -int cmd_config(int argc, const char **argv, const char *prefix) +int cmd_config(int argc, const char **argv, const char *unused_prefix) { int nongit; - char* value; - const char *file = setup_git_directory_gently(&nongit); + char *value; + const char *prefix = setup_git_directory_gently(&nongit); config_exclusive_filename = getenv(CONFIG_ENVIRONMENT); - while (1 < argc) { - if (!strcmp(argv[1], "--int")) - type = T_INT; - else if (!strcmp(argv[1], "--bool")) - type = T_BOOL; - else if (!strcmp(argv[1], "--bool-or-int")) - type = T_BOOL_OR_INT; - else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) { - if (argc != 2) - usage(git_config_set_usage); - if (git_config(show_all_config, NULL) < 0 && - file && errno) - die("unable to read config file %s: %s", file, - strerror(errno)); - return 0; - } - else if (!strcmp(argv[1], "--global")) { - char *home = getenv("HOME"); - if (home) { - char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - config_exclusive_filename = user_config; - } else { - die("$HOME not set"); - } - } - else if (!strcmp(argv[1], "--system")) - config_exclusive_filename = git_etc_gitconfig(); - else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) { - if (argc < 3) - usage(git_config_set_usage); - if (!is_absolute_path(argv[2]) && file) - file = prefix_filename(file, strlen(file), - argv[2]); - else - file = argv[2]; - config_exclusive_filename = file; - argc--; - argv++; - } - else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) { - term = '\0'; - delim = '\n'; - key_delim = '\n'; - } - else if (!strcmp(argv[1], "--rename-section")) { - int ret; - if (argc != 4) - usage(git_config_set_usage); - ret = git_config_rename_section(argv[2], argv[3]); - if (ret < 0) - return ret; - if (ret == 0) { - fprintf(stderr, "No such section!\n"); - return 1; - } - return 0; - } - else if (!strcmp(argv[1], "--remove-section")) { - int ret; - if (argc != 3) - usage(git_config_set_usage); - ret = git_config_rename_section(argv[2], NULL); - if (ret < 0) - return ret; - if (ret == 0) { - fprintf(stderr, "No such section!\n"); - return 1; - } - return 0; - } else if (!strcmp(argv[1], "--get-color")) { - return get_color(argc-2, argv+2); - } else if (!strcmp(argv[1], "--get-colorbool")) { - return get_colorbool(argc-2, argv+2); - } else - break; - argc--; - argv++; - } - - switch (argc) { - case 2: - return get_value(argv[1], NULL); - case 3: - if (!strcmp(argv[1], "--unset")) - return git_config_set(argv[2], NULL); - else if (!strcmp(argv[1], "--unset-all")) - return git_config_set_multivar(argv[2], NULL, NULL, 1); - else if (!strcmp(argv[1], "--get")) - return get_value(argv[2], NULL); - else if (!strcmp(argv[1], "--get-all")) { - do_all = 1; - return get_value(argv[2], NULL); - } else if (!strcmp(argv[1], "--get-regexp")) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; - return get_value(argv[2], NULL); + argc = parse_options(argc, argv, prefix, builtin_config_options, + builtin_config_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (use_global_config + use_system_config + !!given_config_file > 1) { + error("only one config file at a time."); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (use_global_config) { + char *home = getenv("HOME"); + if (home) { + char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); + config_exclusive_filename = user_config; } else { - value = normalize_value(argv[1], argv[2]); - return git_config_set(argv[1], value); + die("$HOME not set"); } - case 4: - if (!strcmp(argv[1], "--unset")) - return git_config_set_multivar(argv[2], NULL, argv[3], 0); - else if (!strcmp(argv[1], "--unset-all")) - return git_config_set_multivar(argv[2], NULL, argv[3], 1); - else if (!strcmp(argv[1], "--get")) - return get_value(argv[2], argv[3]); - else if (!strcmp(argv[1], "--get-all")) { - do_all = 1; - return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--get-regexp")) { - show_keys = 1; - use_key_regexp = 1; - do_all = 1; - return get_value(argv[2], argv[3]); - } else if (!strcmp(argv[1], "--add")) { - value = normalize_value(argv[2], argv[3]); - return git_config_set_multivar(argv[2], value, "^$", 0); - } else if (!strcmp(argv[1], "--replace-all")) { - value = normalize_value(argv[2], argv[3]); - return git_config_set_multivar(argv[2], value, NULL, 1); - } else { - value = normalize_value(argv[1], argv[2]); - return git_config_set_multivar(argv[1], value, argv[3], 0); + } + else if (use_system_config) + config_exclusive_filename = git_etc_gitconfig(); + else if (given_config_file) { + if (!is_absolute_path(given_config_file) && prefix) + config_exclusive_filename = prefix_filename(prefix, + strlen(prefix), + argv[2]); + else + config_exclusive_filename = given_config_file; + } + + if (end_null) { + term = '\0'; + delim = '\n'; + key_delim = '\n'; + } + + if (HAS_MULTI_BITS(types)) { + error("only one type at a time."); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (get_color_slot) + actions |= ACTION_GET_COLOR; + if (get_colorbool_slot) + actions |= ACTION_GET_COLORBOOL; + + if ((get_color_slot || get_colorbool_slot) && types) { + error("--get-color and variable type are incoherent"); + usage_with_options(builtin_config_usage, builtin_config_options); + } + + if (HAS_MULTI_BITS(actions)) { + error("only one action at a time."); + usage_with_options(builtin_config_usage, builtin_config_options); + } + if (actions == 0) + switch (argc) { + case 1: actions = ACTION_GET; break; + case 2: actions = ACTION_SET; break; + case 3: actions = ACTION_SET_ALL; break; + default: + usage_with_options(builtin_config_usage, builtin_config_options); } - case 5: - if (!strcmp(argv[1], "--replace-all")) { - value = normalize_value(argv[2], argv[3]); - return git_config_set_multivar(argv[2], value, argv[4], 1); + + if (actions == ACTION_LIST) { + check_argc(argc, 0, 0); + if (git_config(show_all_config, NULL) < 0) { + if (config_exclusive_filename) + die("unable to read config file %s: %s", + config_exclusive_filename, strerror(errno)); + else + die("error processing config file(s)"); } - case 1: - default: - usage(git_config_set_usage); } + else if (actions == ACTION_EDIT) { + check_argc(argc, 0, 0); + if (!config_exclusive_filename && nongit) + die("not in a git directory"); + git_config(git_default_config, NULL); + launch_editor(config_exclusive_filename ? + config_exclusive_filename : git_path("config"), + NULL, NULL); + } + else if (actions == ACTION_SET) { + check_argc(argc, 2, 2); + value = normalize_value(argv[0], argv[1]); + return git_config_set(argv[0], value); + } + else if (actions == ACTION_SET_ALL) { + check_argc(argc, 2, 3); + value = normalize_value(argv[0], argv[1]); + return git_config_set_multivar(argv[0], value, argv[2], 0); + } + else if (actions == ACTION_ADD) { + check_argc(argc, 2, 2); + value = normalize_value(argv[0], argv[1]); + return git_config_set_multivar(argv[0], value, "^$", 0); + } + else if (actions == ACTION_REPLACE_ALL) { + check_argc(argc, 2, 3); + value = normalize_value(argv[0], argv[1]); + return git_config_set_multivar(argv[0], value, argv[2], 1); + } + else if (actions == ACTION_GET) { + check_argc(argc, 1, 2); + return get_value(argv[0], argv[1]); + } + else if (actions == ACTION_GET_ALL) { + do_all = 1; + check_argc(argc, 1, 2); + return get_value(argv[0], argv[1]); + } + else if (actions == ACTION_GET_REGEXP) { + show_keys = 1; + use_key_regexp = 1; + do_all = 1; + check_argc(argc, 1, 2); + return get_value(argv[0], argv[1]); + } + else if (actions == ACTION_UNSET) { + check_argc(argc, 1, 2); + if (argc == 2) + return git_config_set_multivar(argv[0], NULL, argv[1], 0); + else + return git_config_set(argv[0], NULL); + } + else if (actions == ACTION_UNSET_ALL) { + check_argc(argc, 1, 2); + return git_config_set_multivar(argv[0], NULL, argv[1], 1); + } + else if (actions == ACTION_RENAME_SECTION) { + int ret; + check_argc(argc, 2, 2); + ret = git_config_rename_section(argv[0], argv[1]); + if (ret < 0) + return ret; + if (ret == 0) + die("No such section!"); + } + else if (actions == ACTION_REMOVE_SECTION) { + int ret; + check_argc(argc, 1, 1); + ret = git_config_rename_section(argv[0], NULL); + if (ret < 0) + return ret; + if (ret == 0) + die("No such section!"); + } + else if (actions == ACTION_GET_COLOR) { + get_color(argv[0]); + } + else if (actions == ACTION_GET_COLORBOOL) { + if (argc == 1) + stdout_is_tty = git_config_bool("command line", argv[0]); + else if (argc == 0) + stdout_is_tty = isatty(1); + return get_colorbool(argc != 0); + } + return 0; } diff --git a/builtin-count-objects.c b/builtin-count-objects.c index b814fe5070..1b0b6c84ea 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -83,7 +83,7 @@ int cmd_count_objects(int argc, const char **argv, const char *prefix) OPT_END(), }; - argc = parse_options(argc, argv, opts, count_objects_usage, 0); + argc = parse_options(argc, argv, prefix, opts, count_objects_usage, 0); /* we do not take arguments other than flags for now */ if (argc) usage_with_options(count_objects_usage, opts); diff --git a/builtin-describe.c b/builtin-describe.c index 3a007ed1ca..7a662980d1 100644 --- a/builtin-describe.c +++ b/builtin-describe.c @@ -322,7 +322,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) OPT_END(), }; - argc = parse_options(argc, argv, options, describe_usage, 0); + argc = parse_options(argc, argv, prefix, options, describe_usage, 0); if (max_candidates < 0) max_candidates = 0; else if (max_candidates > MAX_TAGS) @@ -334,7 +334,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) die("--long is incompatible with --abbrev=0"); if (contains) { - const char **args = xmalloc((7 + argc) * sizeof(char*)); + const char **args = xmalloc((7 + argc) * sizeof(char *)); int i = 0; args[i++] = "name-rev"; args[i++] = "--name-only"; @@ -349,7 +349,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) args[i++] = s; } } - memcpy(args + i, argv, argc * sizeof(char*)); + memcpy(args + i, argv, argc * sizeof(char *)); args[i + argc] = NULL; return cmd_name_rev(i + argc, args, prefix); } diff --git a/builtin-diff-tree.c b/builtin-diff-tree.c index 8ecefd4f0f..79cedb72c4 100644 --- a/builtin-diff-tree.c +++ b/builtin-diff-tree.c @@ -102,7 +102,6 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) init_revisions(opt, prefix); git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ - nr_sha1 = 0; opt->abbrev = 0; opt->diff = 1; argc = setup_revisions(argc, argv, opt, NULL); diff --git a/builtin-fast-export.c b/builtin-fast-export.c index f171ee4a2b..6cef810312 100644 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -221,7 +221,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) if (message) message += 2; - if (commit->parents) { + if (commit->parents && + get_object_mark(&commit->parents->item->object) != 0) { parse_commit(commit->parents->item); diff_tree_sha1(commit->parents->item->tree->object.sha1, commit->tree->object.sha1, "", &rev->diffopt); @@ -514,7 +515,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) init_revisions(&revs, prefix); argc = setup_revisions(argc, argv, &revs, NULL); - argc = parse_options(argc, argv, options, fast_export_usage, 0); + argc = parse_options(argc, argv, prefix, options, fast_export_usage, 0); if (argc > 1) usage_with_options (fast_export_usage, options); diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c index 1effdc0d6b..629735f547 100644 --- a/builtin-fetch-pack.c +++ b/builtin-fetch-pack.c @@ -112,7 +112,7 @@ static void mark_common(struct commit *commit, Get the next rev to send, ignoring the common. */ -static const unsigned char* get_rev(void) +static const unsigned char *get_rev(void) { struct commit *commit = NULL; @@ -217,9 +217,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, if (args.depth > 0) { char line[1024]; unsigned char sha1[20]; - int len; - while ((len = packet_read_line(fd[0], line, sizeof(line)))) { + while (packet_read_line(fd[0], line, sizeof(line))) { if (!prefixcmp(line, "shallow ")) { if (get_sha1_hex(line + 8, sha1)) die("invalid shallow line: %s", line); @@ -484,7 +483,9 @@ static int sideband_demux(int fd, void *data) { int *xd = data; - return recv_sideband("fetch-pack", xd[0], fd, 2); + int ret = recv_sideband("fetch-pack", xd[0], fd); + close(fd); + return ret; } static int get_pack(int xd[2], char **pack_lockfile) @@ -612,7 +613,7 @@ static struct ref *do_fetch_pack(int fd[2], /* When cloning, it is not unusual to have * no common commit. */ - fprintf(stderr, "warning: no common commits\n"); + warning("no common commits"); if (get_pack(fd, pack_lockfile)) die("git fetch-pack: fetch failed."); @@ -812,15 +813,13 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, int fd; mtime.sec = st.st_mtime; -#ifdef USE_NSEC - mtime.usec = st.st_mtim.usec; -#endif + mtime.nsec = ST_MTIME_NSEC(st); if (stat(shallow, &st)) { if (mtime.sec) die("shallow file was removed during fetch"); } else if (st.st_mtime != mtime.sec #ifdef USE_NSEC - || st.st_mtim.usec != mtime.usec + || ST_MTIME_NSEC(st) != mtime.nsec #endif ) die("shallow file was changed during fetch"); @@ -828,7 +827,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args, fd = hold_lock_file_for_update(&lock, shallow, LOCK_DIE_ON_ERROR); if (!write_shallow_commits(fd, 0)) { - unlink(shallow); + unlink_or_warn(shallow); rollback_lock_file(&lock); } else { commit_lock_file(&lock); diff --git a/builtin-fetch.c b/builtin-fetch.c index 7fb35fca9d..cd5eb9aff5 100644 --- a/builtin-fetch.c +++ b/builtin-fetch.c @@ -167,6 +167,9 @@ static struct ref *get_ref_map(struct transport *transport, return ref_map; } +#define STORE_REF_ERROR_OTHER 1 +#define STORE_REF_ERROR_DF_CONFLICT 2 + static int s_update_ref(const char *action, struct ref *ref, int check_old) @@ -181,9 +184,11 @@ static int s_update_ref(const char *action, lock = lock_any_ref_for_update(ref->name, check_old ? ref->old_sha1 : NULL, 0); if (!lock) - return 2; + return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : + STORE_REF_ERROR_OTHER; if (write_ref_sha1(lock, ref->new_sha1, msg) < 0) - return 2; + return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT : + STORE_REF_ERROR_OTHER; return 0; } @@ -197,11 +202,7 @@ static int update_local_ref(struct ref *ref, struct commit *current = NULL, *updated; enum object_type type; struct branch *current_branch = branch_get(NULL); - const char *pretty_ref = ref->name + ( - !prefixcmp(ref->name, "refs/heads/") ? 11 : - !prefixcmp(ref->name, "refs/tags/") ? 10 : - !prefixcmp(ref->name, "refs/remotes/") ? 13 : - 0); + const char *pretty_ref = prettify_refname(ref->name); *display = 0; type = sha1_object_info(ref->new_sha1, NULL); @@ -293,7 +294,7 @@ static int update_local_ref(struct ref *ref, } } -static int store_updated_refs(const char *url, const char *remote_name, +static int store_updated_refs(const char *raw_url, const char *remote_name, struct ref *ref_map) { FILE *fp; @@ -302,11 +303,13 @@ static int store_updated_refs(const char *url, const char *remote_name, char note[1024]; const char *what, *kind; struct ref *rm; - char *filename = git_path("FETCH_HEAD"); + char *url, *filename = git_path("FETCH_HEAD"); fp = fopen(filename, "a"); if (!fp) return error("cannot open %s: %s\n", filename, strerror(errno)); + + url = transport_anonymize_url(raw_url); for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; @@ -357,12 +360,18 @@ static int store_updated_refs(const char *url, const char *remote_name, kind); note_len += sprintf(note + note_len, "'%s' of ", what); } - note_len += sprintf(note + note_len, "%.*s", url_len, url); - fprintf(fp, "%s\t%s\t%s\n", + note[note_len] = '\0'; + fprintf(fp, "%s\t%s\t%s", sha1_to_hex(commit ? commit->object.sha1 : rm->old_sha1), rm->merge ? "" : "not-for-merge", note); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + fputs("\\n", fp); + else + fputc(url[i], fp); + fputc('\n', fp); if (ref) rc |= update_local_ref(ref, what, note); @@ -380,8 +389,9 @@ static int store_updated_refs(const char *url, const char *remote_name, fprintf(stderr, " %s\n", note); } } + free(url); fclose(fp); - if (rc & 2) + if (rc & STORE_REF_ERROR_DF_CONFLICT) error("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " "branches", remote_name); @@ -544,7 +554,8 @@ static void check_not_current_branch(struct ref *ref_map) for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && !strcmp(current_branch->refname, ref_map->peer_ref->name)) - die("Refusing to fetch into current branch"); + die("Refusing to fetch into current branch %s " + "of non-bare repository", current_branch->refname); } static int do_fetch(struct transport *transport, @@ -628,7 +639,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) strbuf_addf(&default_rla, " %s", argv[i]); - argc = parse_options(argc, argv, + argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); if (argc == 0) diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index df18f4070f..fbf9582e66 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -256,8 +256,7 @@ static void shortlog(const char *name, unsigned char *sha1, int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { int limit = 20, i = 0, pos = 0; - char line[1024]; - char *p = line, *sep = ""; + char *sep = ""; unsigned char head_sha1[20]; const char *current_branch; @@ -271,9 +270,8 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) { /* get a line */ while (pos < in->len) { int len; - char *newline; + char *newline, *p = in->buf + pos; - p = in->buf + pos; newline = strchr(p, '\n'); len = newline ? newline - p : strlen(p); pos += len + !!newline; @@ -353,7 +351,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_BOOLEAN(0, "log", &merge_summary, "populate log with the shortlog"), OPT_BOOLEAN(0, "summary", &merge_summary, "alias for --log"), - OPT_STRING('F', "file", &inpath, "file", "file to read from"), + OPT_FILENAME('F', "file", &inpath, "file to read from"), OPT_END() }; @@ -362,7 +360,8 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) int ret; git_config(fmt_merge_msg_config, NULL); - argc = parse_options(argc, argv, options, fmt_merge_msg_usage, 0); + argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, + 0); if (argc > 0) usage_with_options(fmt_merge_msg_usage, options); diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c index e46b7adc97..d7cc8cafbf 100644 --- a/builtin-for-each-ref.c +++ b/builtin-for-each-ref.c @@ -8,6 +8,7 @@ #include "blob.h" #include "quote.h" #include "parse-options.h" +#include "remote.h" /* Quoting styles */ #define QUOTE_NONE 0 @@ -66,6 +67,7 @@ static struct { { "subject" }, { "body" }, { "contents" }, + { "upstream" }, }; /* @@ -337,8 +339,11 @@ static const char *copy_name(const char *buf) static const char *copy_email(const char *buf) { const char *email = strchr(buf, '<'); - const char *eoemail = strchr(email, '>'); - if (!email || !eoemail) + const char *eoemail; + if (!email) + return ""; + eoemail = strchr(email, '>'); + if (!eoemail) return ""; return xmemdupz(email, eoemail + 1 - email); } @@ -544,109 +549,6 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v } /* - * generate a format suitable for scanf from a ref_rev_parse_rules - * rule, that is replace the "%.*s" spec with a "%s" spec - */ -static void gen_scanf_fmt(char *scanf_fmt, const char *rule) -{ - char *spec; - - spec = strstr(rule, "%.*s"); - if (!spec || strstr(spec + 4, "%.*s")) - die("invalid rule in ref_rev_parse_rules: %s", rule); - - /* copy all until spec */ - strncpy(scanf_fmt, rule, spec - rule); - scanf_fmt[spec - rule] = '\0'; - /* copy new spec */ - strcat(scanf_fmt, "%s"); - /* copy remaining rule */ - strcat(scanf_fmt, spec + 4); - - return; -} - -/* - * Shorten the refname to an non-ambiguous form - */ -static char *get_short_ref(struct refinfo *ref) -{ - int i; - static char **scanf_fmts; - static int nr_rules; - char *short_name; - - /* pre generate scanf formats from ref_rev_parse_rules[] */ - if (!nr_rules) { - size_t total_len = 0; - - /* the rule list is NULL terminated, count them first */ - for (; ref_rev_parse_rules[nr_rules]; nr_rules++) - /* no +1 because strlen("%s") < strlen("%.*s") */ - total_len += strlen(ref_rev_parse_rules[nr_rules]); - - scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len); - - total_len = 0; - for (i = 0; i < nr_rules; i++) { - scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] - + total_len; - gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]); - total_len += strlen(ref_rev_parse_rules[i]); - } - } - - /* bail out if there are no rules */ - if (!nr_rules) - return ref->refname; - - /* buffer for scanf result, at most ref->refname must fit */ - short_name = xstrdup(ref->refname); - - /* skip first rule, it will always match */ - for (i = nr_rules - 1; i > 0 ; --i) { - int j; - int short_name_len; - - if (1 != sscanf(ref->refname, scanf_fmts[i], short_name)) - continue; - - short_name_len = strlen(short_name); - - /* - * check if the short name resolves to a valid ref, - * but use only rules prior to the matched one - */ - for (j = 0; j < i; j++) { - const char *rule = ref_rev_parse_rules[j]; - unsigned char short_objectname[20]; - char refname[PATH_MAX]; - - /* - * the short name is ambiguous, if it resolves - * (with this previous rule) to a valid ref - * read_ref() returns 0 on success - */ - mksnpath(refname, sizeof(refname), - rule, short_name_len, short_name); - if (!read_ref(refname, short_objectname)) - break; - } - - /* - * short name is non-ambiguous if all previous rules - * haven't resolved to a valid ref - */ - if (j == i) - return short_name; - } - - free(short_name); - return ref->refname; -} - - -/* * Parse the object referred by ref, and grab needed value. */ static void populate_value(struct refinfo *ref) @@ -659,48 +561,74 @@ static void populate_value(struct refinfo *ref) ref->value = xcalloc(sizeof(struct atom_value), used_atom_cnt); - buf = get_obj(ref->objectname, &obj, &size, &eaten); - if (!buf) - die("missing object %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - if (!obj) - die("parse_object_buffer failed on %s for %s", - sha1_to_hex(ref->objectname), ref->refname); - /* Fill in specials first */ for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i]; struct atom_value *v = &ref->value[i]; int deref = 0; + const char *refname; + const char *formatp; + if (*name == '*') { deref = 1; name++; } - if (!prefixcmp(name, "refname")) { - const char *formatp = strchr(name, ':'); - const char *refname = ref->refname; - - /* look for "short" refname format */ - if (formatp) { - formatp++; - if (!strcmp(formatp, "short")) - refname = get_short_ref(ref); - else - die("unknown refname format %s", - formatp); - } - if (!deref) - v->s = refname; - else { - int len = strlen(refname); - char *s = xmalloc(len + 4); - sprintf(s, "%s^{}", refname); - v->s = s; - } + if (!prefixcmp(name, "refname")) + refname = ref->refname; + else if(!prefixcmp(name, "upstream")) { + struct branch *branch; + /* only local branches may have an upstream */ + if (prefixcmp(ref->refname, "refs/heads/")) + continue; + branch = branch_get(ref->refname + 11); + + if (!branch || !branch->merge || !branch->merge[0] || + !branch->merge[0]->dst) + continue; + refname = branch->merge[0]->dst; + } + else + continue; + + formatp = strchr(name, ':'); + /* look for "short" refname format */ + if (formatp) { + formatp++; + if (!strcmp(formatp, "short")) + refname = shorten_unambiguous_ref(refname, + warn_ambiguous_refs); + else + die("unknown %.*s format %s", + (int)(formatp - name), name, formatp); + } + + if (!deref) + v->s = refname; + else { + int len = strlen(refname); + char *s = xmalloc(len + 4); + sprintf(s, "%s^{}", refname); + v->s = s; } } + for (i = 0; i < used_atom_cnt; i++) { + struct atom_value *v = &ref->value[i]; + if (v->s == NULL) + goto need_obj; + } + return; + + need_obj: + buf = get_obj(ref->objectname, &obj, &size, &eaten); + if (!buf) + die("missing object %s for %s", + sha1_to_hex(ref->objectname), ref->refname); + if (!obj) + die("parse_object_buffer failed on %s for %s", + sha1_to_hex(ref->objectname), ref->refname); + grab_values(ref->value, 0, obj, buf, size); if (!eaten) free(buf); @@ -943,7 +871,6 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset) return -1; *sort_tail = s = xcalloc(1, sizeof(*s)); - sort_tail = &s->next; if (*arg == '-') { s->reverse = 1; @@ -986,7 +913,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_END(), }; - parse_options(argc, argv, opts, for_each_ref_usage, 0); + parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); if (maxcount < 0) { error("invalid --count argument: `%d'", maxcount); usage_with_options(for_each_ref_usage, opts); @@ -1002,9 +929,12 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) sort = default_sort(); sort_atom_limit = used_atom_cnt; + /* for warn_ambiguous_refs */ + git_config(git_default_config, NULL); + memset(&cbdata, 0, sizeof(cbdata)); cbdata.grab_pattern = argv; - for_each_ref(grab_single_ref, &cbdata); + for_each_rawref(grab_single_ref, &cbdata); refs = cbdata.grab_array; num_refs = cbdata.grab_cnt; diff --git a/builtin-fsck.c b/builtin-fsck.c index 6436bc2248..7da706cac3 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -590,7 +590,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) errors_found = 0; - argc = parse_options(argc, argv, fsck_opts, fsck_usage, 0); + argc = parse_options(argc, argv, prefix, fsck_opts, fsck_usage, 0); if (write_lost_and_found) { check_full = 1; include_reflogs = 0; diff --git a/builtin-gc.c b/builtin-gc.c index 8d990ed493..7d3e9cc7a0 100644 --- a/builtin-gc.c +++ b/builtin-gc.c @@ -23,7 +23,7 @@ static const char * const builtin_gc_usage[] = { }; static int pack_refs = 1; -static int aggressive_window = -1; +static int aggressive_window = 250; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; static const char *prune_expire = "2.weeks.ago"; @@ -194,12 +194,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (pack_refs < 0) pack_refs = !is_bare_repository(); - argc = parse_options(argc, argv, builtin_gc_options, builtin_gc_usage, 0); + argc = parse_options(argc, argv, prefix, builtin_gc_options, + builtin_gc_usage, 0); if (argc > 0) usage_with_options(builtin_gc_usage, builtin_gc_options); if (aggressive) { append_option(argv_repack, "-f", MAX_ADD); + append_option(argv_repack, "--depth=250", MAX_ADD); if (aggressive_window > 0) { sprintf(buf, "--window=%d", aggressive_window); append_option(argv_repack, buf, MAX_ADD); diff --git a/builtin-grep.c b/builtin-grep.c index 3f12ba3826..73fc922c49 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -10,6 +10,7 @@ #include "tag.h" #include "tree-walk.h" #include "builtin.h" +#include "parse-options.h" #include "grep.h" #ifndef NO_EXTERNAL_GREP @@ -20,7 +21,29 @@ #endif #endif -static int builtin_grep; +static char const * const grep_usage[] = { + "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]", + NULL +}; + +static int grep_config(const char *var, const char *value, void *cb) +{ + struct grep_opt *opt = cb; + + if (!strcmp(var, "color.grep")) { + opt->color = git_config_colorbool(var, value, -1); + return 0; + } + if (!strcmp(var, "color.grep.external")) + return git_config_string(&(opt->color_external), var, value); + if (!strcmp(var, "color.grep.match")) { + if (!value) + return config_error_nonbool(var); + color_parse(value, var, opt->color_match); + return 0; + } + return git_color_default_config(var, value, cb); +} /* * git grep pathspecs are somewhat different from diff-tree pathspecs; @@ -269,6 +292,21 @@ static int flush_grep(struct grep_opt *opt, return status; } +static void grep_add_color(struct strbuf *sb, const char *escape_seq) +{ + size_t orig_len = sb->len; + + while (*escape_seq) { + if (*escape_seq == 'm') + strbuf_addch(sb, ';'); + else if (*escape_seq != '\033' && *escape_seq != '[') + strbuf_addch(sb, *escape_seq); + escape_seq++; + } + if (sb->len > orig_len && sb->buf[sb->len - 1] == ';') + strbuf_setlen(sb, sb->len - 1); +} + static int external_grep(struct grep_opt *opt, const char **paths, int cached) { int i, nr, argc, hit, len, status; @@ -339,6 +377,23 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) push_arg("-e"); push_arg(p->pattern); } + if (opt->color) { + struct strbuf sb = STRBUF_INIT; + + grep_add_color(&sb, opt->color_match); + setenv("GREP_COLOR", sb.buf, 1); + + strbuf_reset(&sb); + strbuf_addstr(&sb, "mt="); + grep_add_color(&sb, opt->color_match); + strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se="); + setenv("GREP_COLORS", sb.buf, 1); + + strbuf_release(&sb); + + if (opt->color_external && strlen(opt->color_external) > 0) + push_arg(opt->color_external); + } hit = 0; argc = nr; @@ -381,7 +436,8 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) } #endif -static int grep_cache(struct grep_opt *opt, const char **paths, int cached) +static int grep_cache(struct grep_opt *opt, const char **paths, int cached, + int external_grep_allowed) { int hit = 0; int nr; @@ -393,7 +449,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) * we grep through the checked-out files. It tends to * be a lot more optimized */ - if (!cached && !builtin_grep) { + if (!cached && external_grep_allowed) { hit = external_grep(opt, paths, cached); if (hit >= 0) return hit; @@ -509,25 +565,182 @@ static int grep_object(struct grep_opt *opt, const char **paths, die("unable to grep from object of type %s", typename(obj->type)); } -static const char builtin_grep_usage[] = -"git grep <option>* [-e] <pattern> <rev>* [[--] <path>...]"; +static int context_callback(const struct option *opt, const char *arg, + int unset) +{ + struct grep_opt *grep_opt = opt->value; + int value; + const char *endp; -static const char emsg_invalid_context_len[] = -"%s: invalid context length argument"; -static const char emsg_missing_context_len[] = -"missing context length argument"; -static const char emsg_missing_argument[] = -"option requires an argument -%s"; + if (unset) { + grep_opt->pre_context = grep_opt->post_context = 0; + return 0; + } + value = strtol(arg, (char **)&endp, 10); + if (*endp) { + return error("switch `%c' expects a numerical value", + opt->short_name); + } + grep_opt->pre_context = grep_opt->post_context = value; + return 0; +} + +static int file_callback(const struct option *opt, const char *arg, int unset) +{ + struct grep_opt *grep_opt = opt->value; + FILE *patterns; + int lno = 0; + struct strbuf sb; + + patterns = fopen(arg, "r"); + if (!patterns) + die("'%s': %s", arg, strerror(errno)); + while (strbuf_getline(&sb, patterns, '\n') == 0) { + /* ignore empty line like grep does */ + if (sb.len == 0) + continue; + append_grep_pattern(grep_opt, strbuf_detach(&sb, NULL), arg, + ++lno, GREP_PATTERN); + } + fclose(patterns); + strbuf_release(&sb); + return 0; +} + +static int not_callback(const struct option *opt, const char *arg, int unset) +{ + struct grep_opt *grep_opt = opt->value; + append_grep_pattern(grep_opt, "--not", "command line", 0, GREP_NOT); + return 0; +} + +static int and_callback(const struct option *opt, const char *arg, int unset) +{ + struct grep_opt *grep_opt = opt->value; + append_grep_pattern(grep_opt, "--and", "command line", 0, GREP_AND); + return 0; +} + +static int open_callback(const struct option *opt, const char *arg, int unset) +{ + struct grep_opt *grep_opt = opt->value; + append_grep_pattern(grep_opt, "(", "command line", 0, GREP_OPEN_PAREN); + return 0; +} + +static int close_callback(const struct option *opt, const char *arg, int unset) +{ + struct grep_opt *grep_opt = opt->value; + append_grep_pattern(grep_opt, ")", "command line", 0, GREP_CLOSE_PAREN); + return 0; +} + +static int pattern_callback(const struct option *opt, const char *arg, + int unset) +{ + struct grep_opt *grep_opt = opt->value; + append_grep_pattern(grep_opt, arg, "-e option", 0, GREP_PATTERN); + return 0; +} + +static int help_callback(const struct option *opt, const char *arg, int unset) +{ + return -1; +} int cmd_grep(int argc, const char **argv, const char *prefix) { int hit = 0; int cached = 0; + int external_grep_allowed = 1; int seen_dashdash = 0; struct grep_opt opt; struct object_array list = { 0, 0, NULL }; const char **paths = NULL; int i; + int dummy; + struct option options[] = { + OPT_BOOLEAN(0, "cached", &cached, + "search in index instead of in the work tree"), + OPT_GROUP(""), + OPT_BOOLEAN('v', "invert-match", &opt.invert, + "show non-matching lines"), + OPT_BIT('i', "ignore-case", &opt.regflags, + "case insensitive matching", REG_ICASE), + OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp, + "match patterns only at word boundaries"), + OPT_SET_INT('a', "text", &opt.binary, + "process binary files as text", GREP_BINARY_TEXT), + OPT_SET_INT('I', NULL, &opt.binary, + "don't match patterns in binary files", + GREP_BINARY_NOMATCH), + OPT_GROUP(""), + OPT_BIT('E', "extended-regexp", &opt.regflags, + "use extended POSIX regular expressions", REG_EXTENDED), + OPT_NEGBIT('G', "basic-regexp", &opt.regflags, + "use basic POSIX regular expressions (default)", + REG_EXTENDED), + OPT_BOOLEAN('F', "fixed-strings", &opt.fixed, + "interpret patterns as fixed strings"), + OPT_GROUP(""), + OPT_BOOLEAN('n', NULL, &opt.linenum, "show line numbers"), + OPT_NEGBIT('h', NULL, &opt.pathname, "don't show filenames", 1), + OPT_BIT('H', NULL, &opt.pathname, "show filenames", 1), + OPT_NEGBIT(0, "full-name", &opt.relative, + "show filenames relative to top directory", 1), + OPT_BOOLEAN('l', "files-with-matches", &opt.name_only, + "show only filenames instead of matching lines"), + OPT_BOOLEAN(0, "name-only", &opt.name_only, + "synonym for --files-with-matches"), + OPT_BOOLEAN('L', "files-without-match", + &opt.unmatch_name_only, + "show only the names of files without match"), + OPT_BOOLEAN('z', "null", &opt.null_following_name, + "print NUL after filenames"), + OPT_BOOLEAN('c', "count", &opt.count, + "show the number of matches instead of matching lines"), + OPT_SET_INT(0, "color", &opt.color, "highlight matches", 1), + OPT_GROUP(""), + OPT_CALLBACK('C', NULL, &opt, "n", + "show <n> context lines before and after matches", + context_callback), + OPT_INTEGER('B', NULL, &opt.pre_context, + "show <n> context lines before matches"), + OPT_INTEGER('A', NULL, &opt.post_context, + "show <n> context lines after matches"), + OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM", + context_callback), + OPT_GROUP(""), + OPT_CALLBACK('f', NULL, &opt, "file", + "read patterns from file", file_callback), + { OPTION_CALLBACK, 'e', NULL, &opt, "pattern", + "match <pattern>", PARSE_OPT_NONEG, pattern_callback }, + { OPTION_CALLBACK, 0, "and", &opt, NULL, + "combine patterns specified with -e", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, + OPT_BOOLEAN(0, "or", &dummy, ""), + { OPTION_CALLBACK, 0, "not", &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback }, + { OPTION_CALLBACK, '(', NULL, &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + open_callback }, + { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + close_callback }, + OPT_BOOLEAN(0, "all-match", &opt.all_match, + "show only matches from files that match all patterns"), + OPT_GROUP(""), +#if NO_EXTERNAL_GREP + OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, + "allow calling of grep(1) (ignored by this build)"), +#else + OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed, + "allow calling of grep(1) (default)"), +#endif + { OPTION_CALLBACK, 0, "help-all", &options, NULL, "show usage", + PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback }, + OPT_END() + }; memset(&opt, 0, sizeof(opt)); opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; @@ -536,6 +749,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix) opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; + strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD); + opt.color = -1; + git_config(grep_config, &opt); + if (opt.color == -1) + opt.color = git_use_color_default; + /* * If there is no -- then the paths must exist in the working * tree. If there is no explicit pattern specified with -e or @@ -546,217 +765,21 @@ int cmd_grep(int argc, const char **argv, const char *prefix) * unrecognized non option is the beginning of the refs list * that continues up to the -- (if exists), and then paths. */ - - while (1 < argc) { - const char *arg = argv[1]; - argc--; argv++; - if (!strcmp("--cached", arg)) { - cached = 1; - continue; - } - if (!strcmp("--no-ext-grep", arg)) { - builtin_grep = 1; - continue; - } - if (!strcmp("-a", arg) || - !strcmp("--text", arg)) { - opt.binary = GREP_BINARY_TEXT; - continue; - } - if (!strcmp("-i", arg) || - !strcmp("--ignore-case", arg)) { - opt.regflags |= REG_ICASE; - continue; - } - if (!strcmp("-I", arg)) { - opt.binary = GREP_BINARY_NOMATCH; - continue; - } - if (!strcmp("-v", arg) || - !strcmp("--invert-match", arg)) { - opt.invert = 1; - continue; - } - if (!strcmp("-E", arg) || - !strcmp("--extended-regexp", arg)) { - opt.regflags |= REG_EXTENDED; - continue; - } - if (!strcmp("-F", arg) || - !strcmp("--fixed-strings", arg)) { - opt.fixed = 1; - continue; - } - if (!strcmp("-G", arg) || - !strcmp("--basic-regexp", arg)) { - opt.regflags &= ~REG_EXTENDED; - continue; - } - if (!strcmp("-n", arg)) { - opt.linenum = 1; - continue; - } - if (!strcmp("-h", arg)) { - opt.pathname = 0; - continue; - } - if (!strcmp("-H", arg)) { - opt.pathname = 1; - continue; - } - if (!strcmp("-l", arg) || - !strcmp("--name-only", arg) || - !strcmp("--files-with-matches", arg)) { - opt.name_only = 1; - continue; - } - if (!strcmp("-L", arg) || - !strcmp("--files-without-match", arg)) { - opt.unmatch_name_only = 1; - continue; - } - if (!strcmp("-z", arg) || - !strcmp("--null", arg)) { - opt.null_following_name = 1; - continue; - } - if (!strcmp("-c", arg) || - !strcmp("--count", arg)) { - opt.count = 1; - continue; - } - if (!strcmp("-w", arg) || - !strcmp("--word-regexp", arg)) { - opt.word_regexp = 1; - continue; - } - if (!prefixcmp(arg, "-A") || - !prefixcmp(arg, "-B") || - !prefixcmp(arg, "-C") || - (arg[0] == '-' && '1' <= arg[1] && arg[1] <= '9')) { - unsigned num; - const char *scan; - switch (arg[1]) { - case 'A': case 'B': case 'C': - if (!arg[2]) { - if (argc <= 1) - die(emsg_missing_context_len); - scan = *++argv; - argc--; - } - else - scan = arg + 2; - break; - default: - scan = arg + 1; - break; - } - if (strtoul_ui(scan, 10, &num)) - die(emsg_invalid_context_len, scan); - switch (arg[1]) { - case 'A': - opt.post_context = num; - break; - default: - case 'C': - opt.post_context = num; - case 'B': - opt.pre_context = num; - break; - } - continue; - } - if (!strcmp("-f", arg)) { - FILE *patterns; - int lno = 0; - char buf[1024]; - if (argc <= 1) - die(emsg_missing_argument, arg); - patterns = fopen(argv[1], "r"); - if (!patterns) - die("'%s': %s", argv[1], strerror(errno)); - while (fgets(buf, sizeof(buf), patterns)) { - int len = strlen(buf); - if (len && buf[len-1] == '\n') - buf[len-1] = 0; - /* ignore empty line like grep does */ - if (!buf[0]) - continue; - append_grep_pattern(&opt, xstrdup(buf), - argv[1], ++lno, - GREP_PATTERN); - } - fclose(patterns); - argv++; - argc--; - continue; - } - if (!strcmp("--not", arg)) { - append_grep_pattern(&opt, arg, "command line", 0, - GREP_NOT); - continue; - } - if (!strcmp("--and", arg)) { - append_grep_pattern(&opt, arg, "command line", 0, - GREP_AND); - continue; - } - if (!strcmp("--or", arg)) - continue; /* no-op */ - if (!strcmp("(", arg)) { - append_grep_pattern(&opt, arg, "command line", 0, - GREP_OPEN_PAREN); - continue; - } - if (!strcmp(")", arg)) { - append_grep_pattern(&opt, arg, "command line", 0, - GREP_CLOSE_PAREN); - continue; - } - if (!strcmp("--all-match", arg)) { - opt.all_match = 1; - continue; - } - if (!strcmp("-e", arg)) { - if (1 < argc) { - append_grep_pattern(&opt, argv[1], - "-e option", 0, - GREP_PATTERN); - argv++; - argc--; - continue; - } - die(emsg_missing_argument, arg); - } - if (!strcmp("--full-name", arg)) { - opt.relative = 0; - continue; - } - if (!strcmp("--", arg)) { - /* later processing wants to have this at argv[1] */ - argv--; - argc++; - break; - } - if (*arg == '-') - usage(builtin_grep_usage); - - /* First unrecognized non-option token */ - if (!opt.pattern_list) { - append_grep_pattern(&opt, arg, "command line", 0, - GREP_PATTERN); - break; - } - else { - /* We are looking at the first path or rev; - * it is found at argv[1] after leaving the - * loop. - */ - argc++; argv--; - break; - } + argc = parse_options(argc, argv, prefix, options, grep_usage, + PARSE_OPT_KEEP_DASHDASH | + PARSE_OPT_STOP_AT_NON_OPTION | + PARSE_OPT_NO_INTERNAL_HELP); + + /* First unrecognized non-option token */ + if (argc > 0 && !opt.pattern_list) { + append_grep_pattern(&opt, argv[0], "command line", 0, + GREP_PATTERN); + argv++; + argc--; } + if (opt.color && !opt.color_external) + external_grep_allowed = 0; if (!opt.pattern_list) die("no pattern given."); if ((opt.regflags != REG_NEWLINE) && opt.fixed) @@ -764,7 +787,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) compile_grep_patterns(&opt); /* Check revs and then paths */ - for (i = 1; i < argc; i++) { + for (i = 0; i < argc; i++) { const char *arg = argv[i]; unsigned char sha1[20]; /* Is it a rev? */ @@ -807,7 +830,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!list.nr) { if (!cached) setup_work_tree(); - return !grep_cache(&opt, paths, cached); + return !grep_cache(&opt, paths, cached, external_grep_allowed); } if (cached) diff --git a/builtin-help.c b/builtin-help.c index 9b57a74618..6e53b23833 100644 --- a/builtin-help.c +++ b/builtin-help.c @@ -80,10 +80,9 @@ static int check_emacsclient_version(void) ec_process.argv = argv_ec; ec_process.err = -1; ec_process.stdout_to_stderr = 1; - if (start_command(&ec_process)) { - fprintf(stderr, "Failed to start emacsclient.\n"); - return -1; - } + if (start_command(&ec_process)) + return error("Failed to start emacsclient."); + strbuf_read(&buffer, ec_process.err, 20); close(ec_process.err); @@ -94,27 +93,24 @@ static int check_emacsclient_version(void) finish_command(&ec_process); if (prefixcmp(buffer.buf, "emacsclient")) { - fprintf(stderr, "Failed to parse emacsclient version.\n"); strbuf_release(&buffer); - return -1; + return error("Failed to parse emacsclient version."); } strbuf_remove(&buffer, 0, strlen("emacsclient")); version = atoi(buffer.buf); if (version < 22) { - fprintf(stderr, - "emacsclient version '%d' too old (< 22).\n", - version); strbuf_release(&buffer); - return -1; + return error("emacsclient version '%d' too old (< 22).", + version); } strbuf_release(&buffer); return 0; } -static void exec_woman_emacs(const char* path, const char *page) +static void exec_woman_emacs(const char *path, const char *page) { if (!check_emacsclient_version()) { /* This works only with emacsclient version >= 22. */ @@ -128,7 +124,7 @@ static void exec_woman_emacs(const char* path, const char *page) } } -static void exec_man_konqueror(const char* path, const char *page) +static void exec_man_konqueror(const char *path, const char *page) { const char *display = getenv("DISPLAY"); if (display && *display) { @@ -156,7 +152,7 @@ static void exec_man_konqueror(const char* path, const char *page) } } -static void exec_man_man(const char* path, const char *page) +static void exec_man_man(const char *path, const char *page) { if (!path) path = "man"; @@ -236,7 +232,7 @@ static int add_man_viewer_info(const char *var, const char *value) const char *subkey = strrchr(name, '.'); if (!subkey) - return error("Config with no key for man viewer: %s", name); + return 0; if (!strcmp(subkey, ".path")) { if (!value) @@ -249,7 +245,6 @@ static int add_man_viewer_info(const char *var, const char *value) return add_man_viewer_cmd(name, subkey - name, value); } - warning("'%s': unsupported man viewer sub key.", subkey); return 0; } @@ -424,7 +419,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) setup_git_directory_gently(&nongit); git_config(git_help_config, NULL); - argc = parse_options(argc, argv, builtin_help_options, + argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); if (show_all) { diff --git a/builtin-init-db.c b/builtin-init-db.c index bfdfc7b411..d1fa12a59e 100644 --- a/builtin-init-db.c +++ b/builtin-init-db.c @@ -132,8 +132,7 @@ static void copy_templates(const char *template_dir) } dir = opendir(template_path); if (!dir) { - fprintf(stderr, "warning: templates not found %s\n", - template_dir); + warning("templates not found %s", template_dir); return; } @@ -146,8 +145,8 @@ static void copy_templates(const char *template_dir) if (repository_format_version && repository_format_version != GIT_REPO_VERSION) { - fprintf(stderr, "warning: not copying templates of " - "a wrong format version %d from '%s'\n", + warning("not copying templates of " + "a wrong format version %d from '%s'", repository_format_version, template_dir); closedir(dir); diff --git a/builtin-log.c b/builtin-log.c index 0f0adf2bab..0d34050556 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -17,6 +17,8 @@ #include "run-command.h" #include "shortlog.h" #include "remote.h" +#include "string-list.h" +#include "parse-options.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -416,18 +418,13 @@ int cmd_log(int argc, const char **argv, const char *prefix) } /* format-patch */ -#define FORMAT_PATCH_NAME_MAX 64 - -static int istitlechar(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '.' || c == '_'; -} static const char *fmt_patch_suffix = ".patch"; static int numbered = 0; static int auto_number = 1; +static char *default_attach = NULL; + static char **extra_hdr; static int extra_hdr_nr; static int extra_hdr_alloc; @@ -459,6 +456,11 @@ static void add_header(const char *value) extra_hdr[extra_hdr_nr++] = xstrndup(value, len); } +#define THREAD_SHALLOW 1 +#define THREAD_DEEP 2 +static int thread = 0; +static int do_signoff = 0; + static int git_format_config(const char *var, const char *value, void *cb) { if (!strcmp(var, "format.headers")) { @@ -488,94 +490,60 @@ static int git_format_config(const char *var, const char *value, void *cb) auto_number = auto_number && numbered; return 0; } - - return git_log_config(var, value, cb); -} - - -static const char *get_oneline_for_filename(struct commit *commit, - int keep_subject) -{ - static char filename[PATH_MAX]; - char *sol; - int len = 0; - int suffix_len = strlen(fmt_patch_suffix) + 1; - - sol = strstr(commit->buffer, "\n\n"); - if (!sol) - filename[0] = '\0'; - else { - int j, space = 0; - - sol += 2; - /* strip [PATCH] or [PATCH blabla] */ - if (!keep_subject && !prefixcmp(sol, "[PATCH")) { - char *eos = strchr(sol + 6, ']'); - if (eos) { - while (isspace(*eos)) - eos++; - sol = eos; - } + if (!strcmp(var, "format.attach")) { + if (value && *value) + default_attach = xstrdup(value); + else + default_attach = xstrdup(git_version_string); + return 0; + } + if (!strcmp(var, "format.thread")) { + if (value && !strcasecmp(value, "deep")) { + thread = THREAD_DEEP; + return 0; } - - for (j = 0; - j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 && - len < sizeof(filename) - suffix_len && - sol[j] && sol[j] != '\n'; - j++) { - if (istitlechar(sol[j])) { - if (space) { - filename[len++] = '-'; - space = 0; - } - filename[len++] = sol[j]; - if (sol[j] == '.') - while (sol[j + 1] == '.') - j++; - } else - space = 1; + if (value && !strcasecmp(value, "shallow")) { + thread = THREAD_SHALLOW; + return 0; } - while (filename[len - 1] == '.' - || filename[len - 1] == '-') - len--; - filename[len] = '\0'; + thread = git_config_bool(var, value) && THREAD_SHALLOW; + return 0; + } + if (!strcmp(var, "format.signoff")) { + do_signoff = git_config_bool(var, value); + return 0; } - return filename; + + return git_log_config(var, value, cb); } static FILE *realstdout = NULL; static const char *output_directory = NULL; static int outdir_offset; -static int reopen_stdout(const char *oneline, int nr, int total) +static int reopen_stdout(struct commit *commit, struct rev_info *rev) { - char filename[PATH_MAX]; - int len = 0; + struct strbuf filename = STRBUF_INIT; int suffix_len = strlen(fmt_patch_suffix) + 1; if (output_directory) { - len = snprintf(filename, sizeof(filename), "%s", - output_directory); - if (len >= - sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len) + strbuf_addstr(&filename, output_directory); + if (filename.len >= + PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) return error("name of output directory is too long"); - if (filename[len - 1] != '/') - filename[len++] = '/'; + if (filename.buf[filename.len - 1] != '/') + strbuf_addch(&filename, '/'); } - if (!oneline) - len += sprintf(filename + len, "%d", nr); - else { - len += sprintf(filename + len, "%04d-", nr); - len += snprintf(filename + len, sizeof(filename) - len - 1 - - suffix_len, "%s", oneline); - strcpy(filename + len, fmt_patch_suffix); - } + get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename); + + if (!DIFF_OPT_TST(&rev->diffopt, QUIET)) + fprintf(realstdout, "%s\n", filename.buf + outdir_offset); - fprintf(realstdout, "%s\n", filename + outdir_offset); - if (freopen(filename, "w", stdout) == NULL) - return error("Cannot open patch file %s",filename); + if (freopen(filename.buf, "w", stdout) == NULL) + return error("Cannot open patch file %s", filename.buf); + strbuf_release(&filename); return 0; } @@ -645,7 +613,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, int nr, struct commit **list, struct commit *head) { const char *committer; - char *head_sha1; const char *subject_start = NULL; const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; const char *msg; @@ -653,23 +620,43 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; - const char *encoding = "utf-8"; + const char *encoding = "UTF-8"; struct diff_options opts; int need_8bit_cte = 0; + struct commit *commit = NULL; if (rev->commit_format != CMIT_FMT_EMAIL) die("Cover letter needs email format"); - if (!use_stdout && reopen_stdout(numbered_files ? - NULL : "cover-letter", 0, rev->total)) + committer = git_committer_info(0); + + if (!numbered_files) { + /* + * We fake a commit for the cover letter so we get the filename + * desired. + */ + commit = xcalloc(1, sizeof(*commit)); + commit->buffer = xmalloc(400); + snprintf(commit->buffer, 400, + "tree 0000000000000000000000000000000000000000\n" + "parent %s\n" + "author %s\n" + "committer %s\n\n" + "cover letter\n", + sha1_to_hex(head->object.sha1), committer, committer); + } + + if (!use_stdout && reopen_stdout(commit, rev)) return; - head_sha1 = sha1_to_hex(head->object.sha1); + if (commit) { - log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers, - &need_8bit_cte); + free(commit->buffer); + free(commit); + } - committer = git_committer_info(0); + log_write_email_headers(rev, head, &subject_start, &extra_headers, + &need_8bit_cte); msg = body; pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822, @@ -754,19 +741,120 @@ static const char *set_outdir(const char *prefix, const char *output_directory) output_directory)); } +static const char * const builtin_format_patch_usage[] = { + "git format-patch [options] [<since> | <revision range>]", + NULL +}; + +static int keep_subject = 0; + +static int keep_callback(const struct option *opt, const char *arg, int unset) +{ + ((struct rev_info *)opt->value)->total = -1; + keep_subject = 1; + return 0; +} + +static int subject_prefix = 0; + +static int subject_prefix_callback(const struct option *opt, const char *arg, + int unset) +{ + subject_prefix = 1; + ((struct rev_info *)opt->value)->subject_prefix = arg; + return 0; +} + +static int numbered_cmdline_opt = 0; + +static int numbered_callback(const struct option *opt, const char *arg, + int unset) +{ + *(int *)opt->value = numbered_cmdline_opt = unset ? 0 : 1; + if (unset) + auto_number = 0; + return 0; +} + +static int no_numbered_callback(const struct option *opt, const char *arg, + int unset) +{ + return numbered_callback(opt, arg, 1); +} + +static int output_directory_callback(const struct option *opt, const char *arg, + int unset) +{ + const char **dir = (const char **)opt->value; + if (*dir) + die("Two output directories?"); + *dir = arg; + return 0; +} + +static int thread_callback(const struct option *opt, const char *arg, int unset) +{ + int *thread = (int *)opt->value; + if (unset) + *thread = 0; + else if (!arg || !strcmp(arg, "shallow")) + *thread = THREAD_SHALLOW; + else if (!strcmp(arg, "deep")) + *thread = THREAD_DEEP; + else + return 1; + return 0; +} + +static int attach_callback(const struct option *opt, const char *arg, int unset) +{ + struct rev_info *rev = (struct rev_info *)opt->value; + if (unset) + rev->mime_boundary = NULL; + else if (arg) + rev->mime_boundary = arg; + else + rev->mime_boundary = git_version_string; + rev->no_inline = unset ? 0 : 1; + return 0; +} + +static int inline_callback(const struct option *opt, const char *arg, int unset) +{ + struct rev_info *rev = (struct rev_info *)opt->value; + if (unset) + rev->mime_boundary = NULL; + else if (arg) + rev->mime_boundary = arg; + else + rev->mime_boundary = git_version_string; + rev->no_inline = 0; + return 0; +} + +static int header_callback(const struct option *opt, const char *arg, int unset) +{ + add_header(arg); + return 0; +} + +static int cc_callback(const struct option *opt, const char *arg, int unset) +{ + ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); + extra_cc[extra_cc_nr++] = xstrdup(arg); + return 0; +} + int cmd_format_patch(int argc, const char **argv, const char *prefix) { struct commit *commit; struct commit **list = NULL; struct rev_info rev; - int nr = 0, total, i, j; + int nr = 0, total, i; int use_stdout = 0; int start_number = -1; - int keep_subject = 0; int numbered_files = 0; /* _just_ numbers */ - int subject_prefix = 0; int ignore_if_in_upstream = 0; - int thread = 0; int cover_letter = 0; int boundary_count = 0; int no_binary_diff = 0; @@ -775,6 +863,57 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct patch_ids ids; char *add_signoff = NULL; struct strbuf buf = STRBUF_INIT; + const struct option builtin_format_patch_options[] = { + { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, + "use [PATCH n/m] even with a single patch", + PARSE_OPT_NOARG, numbered_callback }, + { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, + "use [PATCH] even with multiple patches", + PARSE_OPT_NOARG, no_numbered_callback }, + OPT_BOOLEAN('s', "signoff", &do_signoff, "add Signed-off-by:"), + OPT_BOOLEAN(0, "stdout", &use_stdout, + "print patches to standard out"), + OPT_BOOLEAN(0, "cover-letter", &cover_letter, + "generate a cover letter"), + OPT_BOOLEAN(0, "numbered-files", &numbered_files, + "use simple number sequence for output file names"), + OPT_STRING(0, "suffix", &fmt_patch_suffix, "sfx", + "use <sfx> instead of '.patch'"), + OPT_INTEGER(0, "start-number", &start_number, + "start numbering patches at <n> instead of 1"), + { OPTION_CALLBACK, 0, "subject-prefix", &rev, "prefix", + "Use [<prefix>] instead of [PATCH]", + PARSE_OPT_NONEG, subject_prefix_callback }, + { OPTION_CALLBACK, 'o', "output-directory", &output_directory, + "dir", "store resulting files in <dir>", + PARSE_OPT_NONEG, output_directory_callback }, + { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, + "don't strip/add [PATCH]", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, + OPT_BOOLEAN(0, "no-binary", &no_binary_diff, + "don't output binary diffs"), + OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream, + "don't include a patch matching a commit upstream"), + OPT_GROUP("Messaging"), + { OPTION_CALLBACK, 0, "add-header", NULL, "header", + "add email header", PARSE_OPT_NONEG, + header_callback }, + { OPTION_CALLBACK, 0, "cc", NULL, "email", "add Cc: header", + PARSE_OPT_NONEG, cc_callback }, + OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id", + "make first mail a reply to <message-id>"), + { OPTION_CALLBACK, 0, "attach", &rev, "boundary", + "attach the patch", PARSE_OPT_OPTARG, + attach_callback }, + { OPTION_CALLBACK, 0, "inline", &rev, "boundary", + "inline the patch", + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + inline_callback }, + { OPTION_CALLBACK, 0, "thread", &thread, "style", + "enable message threading, styles: shallow, deep", + PARSE_OPT_OPTARG, thread_callback }, + OPT_END() + }; git_config(git_format_config, NULL); init_revisions(&rev, prefix); @@ -787,100 +926,29 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.subject_prefix = fmt_patch_subject_prefix; + if (default_attach) { + rev.mime_boundary = default_attach; + rev.no_inline = 1; + } + /* * Parse the arguments before setup_revisions(), or something * like "git format-patch -o a123 HEAD^.." may fail; a123 is * possibly a valid SHA1. */ - for (i = 1, j = 1; i < argc; i++) { - if (!strcmp(argv[i], "--stdout")) - use_stdout = 1; - else if (!strcmp(argv[i], "-n") || - !strcmp(argv[i], "--numbered")) - numbered = 1; - else if (!strcmp(argv[i], "-N") || - !strcmp(argv[i], "--no-numbered")) { - numbered = 0; - auto_number = 0; - } - else if (!prefixcmp(argv[i], "--start-number=")) - start_number = strtol(argv[i] + 15, NULL, 10); - else if (!strcmp(argv[i], "--numbered-files")) - numbered_files = 1; - else if (!strcmp(argv[i], "--start-number")) { - i++; - if (i == argc) - die("Need a number for --start-number"); - start_number = strtol(argv[i], NULL, 10); - } - else if (!prefixcmp(argv[i], "--cc=")) { - ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc); - extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5); - } - else if (!strcmp(argv[i], "-k") || - !strcmp(argv[i], "--keep-subject")) { - keep_subject = 1; - rev.total = -1; - } - else if (!strcmp(argv[i], "--output-directory") || - !strcmp(argv[i], "-o")) { - i++; - if (argc <= i) - die("Which directory?"); - if (output_directory) - die("Two output directories?"); - output_directory = argv[i]; - } - else if (!strcmp(argv[i], "--signoff") || - !strcmp(argv[i], "-s")) { - const char *committer; - const char *endpos; - committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); - endpos = strchr(committer, '>'); - if (!endpos) - die("bogus committer info %s", committer); - add_signoff = xmemdupz(committer, endpos - committer + 1); - } - else if (!strcmp(argv[i], "--attach")) { - rev.mime_boundary = git_version_string; - rev.no_inline = 1; - } - else if (!prefixcmp(argv[i], "--attach=")) { - rev.mime_boundary = argv[i] + 9; - rev.no_inline = 1; - } - else if (!strcmp(argv[i], "--inline")) { - rev.mime_boundary = git_version_string; - rev.no_inline = 0; - } - else if (!prefixcmp(argv[i], "--inline=")) { - rev.mime_boundary = argv[i] + 9; - rev.no_inline = 0; - } - else if (!strcmp(argv[i], "--ignore-if-in-upstream")) - ignore_if_in_upstream = 1; - else if (!strcmp(argv[i], "--thread")) - thread = 1; - else if (!prefixcmp(argv[i], "--in-reply-to=")) - in_reply_to = argv[i] + 14; - else if (!strcmp(argv[i], "--in-reply-to")) { - i++; - if (i == argc) - die("Need a Message-Id for --in-reply-to"); - in_reply_to = argv[i]; - } else if (!prefixcmp(argv[i], "--subject-prefix=")) { - subject_prefix = 1; - rev.subject_prefix = argv[i] + 17; - } else if (!prefixcmp(argv[i], "--suffix=")) - fmt_patch_suffix = argv[i] + 9; - else if (!strcmp(argv[i], "--cover-letter")) - cover_letter = 1; - else if (!strcmp(argv[i], "--no-binary")) - no_binary_diff = 1; - else - argv[j++] = argv[i]; + argc = parse_options(argc, argv, prefix, builtin_format_patch_options, + builtin_format_patch_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN); + + if (do_signoff) { + const char *committer; + const char *endpos; + committer = git_committer_info(IDENT_ERROR_ON_NO_NAME); + endpos = strchr(committer, '>'); + if (!endpos) + die("bogus committer info %s", committer); + add_signoff = xmemdupz(committer, endpos - committer + 1); } - argc = j; for (i = 0; i < extra_hdr_nr; i++) { strbuf_addstr(&buf, extra_hdr[i]); @@ -913,6 +981,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (start_number < 0) start_number = 1; + + /* + * If numbered is set solely due to format.numbered in config, + * and it would conflict with --keep-subject (-k) from the + * command line, reset "numbered". + */ + if (numbered && keep_subject && !numbered_cmdline_opt) + numbered = 0; + if (numbered && keep_subject) die ("-n and -k are mutually exclusive."); if (keep_subject && subject_prefix) @@ -1009,8 +1086,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 1; if (numbered) rev.total = total + start_number - 1; - if (in_reply_to) - rev.ref_message_id = clean_message_id(in_reply_to); + if (in_reply_to || thread || cover_letter) + rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); + if (in_reply_to) { + const char *msgid = clean_message_id(in_reply_to); + string_list_append(msgid, rev.ref_message_ids); + } + rev.numbered_files = numbered_files; + rev.patch_suffix = fmt_patch_suffix; if (cover_letter) { if (thread) gen_message_id(&rev, "cover"); @@ -1029,21 +1112,39 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) /* Have we already had a message ID? */ if (rev.message_id) { /* - * If we've got the ID to be a reply - * to, discard the current ID; - * otherwise, make everything a reply - * to that. + * For deep threading: make every mail + * a reply to the previous one, no + * matter what other options are set. + * + * For shallow threading: + * + * Without --cover-letter and + * --in-reply-to, make every mail a + * reply to the one before. + * + * With --in-reply-to but no + * --cover-letter, make every mail a + * reply to the <reply-to>. + * + * With --cover-letter, make every + * mail but the cover letter a reply + * to the cover letter. The cover + * letter is a reply to the + * --in-reply-to, if specified. */ - if (rev.ref_message_id) + if (thread == THREAD_SHALLOW + && rev.ref_message_ids->nr > 0 + && (!cover_letter || rev.nr > 1)) free(rev.message_id); else - rev.ref_message_id = rev.message_id; + string_list_append(rev.message_id, + rev.ref_message_ids); } gen_message_id(&rev, sha1_to_hex(commit->object.sha1)); } - if (!use_stdout && reopen_stdout(numbered_files ? NULL : - get_oneline_for_filename(commit, keep_subject), - rev.nr, rev.total)) + + if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit, + &rev)) die("Failed to create output files"); shown = log_tree_commit(&rev, commit); free(commit->buffer); diff --git a/builtin-ls-files.c b/builtin-ls-files.c index ca6f33d046..2312866605 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -10,6 +10,7 @@ #include "dir.h" #include "builtin.h" #include "tree.h" +#include "parse-options.h" static int abbrev; static int show_deleted; @@ -28,6 +29,7 @@ static const char **pathspec; static int error_unmatch; static char *ps_matched; static const char *with_tree; +static int exc_given; static const char *tag_cached = ""; static const char *tag_unmerged = ""; @@ -174,7 +176,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != dir->show_ignored) + if (excluded(dir, ce->name, &dtype) != + !!(dir->flags & DIR_SHOW_IGNORED)) continue; if (show_unmerged && !ce_stage(ce)) continue; @@ -189,7 +192,8 @@ static void show_files(struct dir_struct *dir, const char *prefix) struct stat st; int err; int dtype = ce_to_dtype(ce); - if (excluded(dir, ce->name, &dtype) != dir->show_ignored) + if (excluded(dir, ce->name, &dtype) != + !!(dir->flags & DIR_SHOW_IGNORED)) continue; if (ce->ce_flags & CE_UPDATE) continue; @@ -374,159 +378,141 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_ return errors; } -static const char ls_files_usage[] = - "git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* " - "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] " - "[ --exclude-per-directory=<filename> ] [--exclude-standard] " - "[--full-name] [--abbrev] [--] [<file>]*"; +static const char * const ls_files_usage[] = { + "git ls-files [options] [<file>]*", + NULL +}; + +static int option_parse_z(const struct option *opt, + const char *arg, int unset) +{ + line_terminator = unset ? '\n' : '\0'; + + return 0; +} + +static int option_parse_exclude(const struct option *opt, + const char *arg, int unset) +{ + struct exclude_list *list = opt->value; + + exc_given = 1; + add_exclude(arg, "", 0, list); + + return 0; +} + +static int option_parse_exclude_from(const struct option *opt, + const char *arg, int unset) +{ + struct dir_struct *dir = opt->value; + + exc_given = 1; + add_excludes_from_file(dir, arg); + + return 0; +} + +static int option_parse_exclude_standard(const struct option *opt, + const char *arg, int unset) +{ + struct dir_struct *dir = opt->value; + + exc_given = 1; + setup_standard_excludes(dir); + + return 0; +} int cmd_ls_files(int argc, const char **argv, const char *prefix) { - int i; - int exc_given = 0, require_work_tree = 0; + int require_work_tree = 0, show_tag = 0; struct dir_struct dir; + struct option builtin_ls_files_options[] = { + { OPTION_CALLBACK, 'z', NULL, NULL, NULL, + "paths are separated with NUL character", + PARSE_OPT_NOARG, option_parse_z }, + OPT_BOOLEAN('t', NULL, &show_tag, + "identify the file status with tags"), + OPT_BOOLEAN('v', NULL, &show_valid_bit, + "use lowercase letters for 'assume unchanged' files"), + OPT_BOOLEAN('c', "cached", &show_cached, + "show cached files in the output (default)"), + OPT_BOOLEAN('d', "deleted", &show_deleted, + "show deleted files in the output"), + OPT_BOOLEAN('m', "modified", &show_modified, + "show modified files in the output"), + OPT_BOOLEAN('o', "others", &show_others, + "show other files in the output"), + OPT_BIT('i', "ignored", &dir.flags, + "show ignored files in the output", + DIR_SHOW_IGNORED), + OPT_BOOLEAN('s', "stage", &show_stage, + "show staged contents' object name in the output"), + OPT_BOOLEAN('k', "killed", &show_killed, + "show files on the filesystem that need to be removed"), + OPT_BIT(0, "directory", &dir.flags, + "show 'other' directories' name only", + DIR_SHOW_OTHER_DIRECTORIES), + OPT_NEGBIT(0, "empty-directory", &dir.flags, + "don't show empty directories", + DIR_HIDE_EMPTY_DIRECTORIES), + OPT_BOOLEAN('u', "unmerged", &show_unmerged, + "show unmerged files in the output"), + { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern", + "skip files matching pattern", + 0, option_parse_exclude }, + { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file", + "exclude patterns are read from <file>", + 0, option_parse_exclude_from }, + OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file", + "read additional per-directory exclude patterns in <file>"), + { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, + "add the standard git exclusions", + PARSE_OPT_NOARG, option_parse_exclude_standard }, + { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL, + "make the output relative to the project top directory", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL }, + OPT_BOOLEAN(0, "error-unmatch", &error_unmatch, + "if any <file> is not in the index, treat this as an error"), + OPT_STRING(0, "with-tree", &with_tree, "tree-ish", + "pretend that paths removed since <tree-ish> are still present"), + OPT__ABBREV(&abbrev), + OPT_END() + }; memset(&dir, 0, sizeof(dir)); if (prefix) prefix_offset = strlen(prefix); git_config(git_default_config, NULL); - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--")) { - i++; - break; - } - if (!strcmp(arg, "-z")) { - line_terminator = 0; - continue; - } - if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) { - tag_cached = "H "; - tag_unmerged = "M "; - tag_removed = "R "; - tag_modified = "C "; - tag_other = "? "; - tag_killed = "K "; - if (arg[1] == 'v') - show_valid_bit = 1; - continue; - } - if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) { - show_cached = 1; - continue; - } - if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) { - show_deleted = 1; - require_work_tree = 1; - continue; - } - if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) { - show_modified = 1; - require_work_tree = 1; - continue; - } - if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) { - show_others = 1; - require_work_tree = 1; - continue; - } - if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) { - dir.show_ignored = 1; - require_work_tree = 1; - continue; - } - if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) { - show_stage = 1; - continue; - } - if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) { - show_killed = 1; - require_work_tree = 1; - continue; - } - if (!strcmp(arg, "--directory")) { - dir.show_other_directories = 1; - continue; - } - if (!strcmp(arg, "--no-empty-directory")) { - dir.hide_empty_directories = 1; - continue; - } - if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) { - /* There's no point in showing unmerged unless - * you also show the stage information. - */ - show_stage = 1; - show_unmerged = 1; - continue; - } - if (!strcmp(arg, "-x") && i+1 < argc) { - exc_given = 1; - add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]); - continue; - } - if (!prefixcmp(arg, "--exclude=")) { - exc_given = 1; - add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]); - continue; - } - if (!strcmp(arg, "-X") && i+1 < argc) { - exc_given = 1; - add_excludes_from_file(&dir, argv[++i]); - continue; - } - if (!prefixcmp(arg, "--exclude-from=")) { - exc_given = 1; - add_excludes_from_file(&dir, arg+15); - continue; - } - if (!prefixcmp(arg, "--exclude-per-directory=")) { - exc_given = 1; - dir.exclude_per_dir = arg + 24; - continue; - } - if (!strcmp(arg, "--exclude-standard")) { - exc_given = 1; - setup_standard_excludes(&dir); - continue; - } - if (!strcmp(arg, "--full-name")) { - prefix_offset = 0; - continue; - } - if (!strcmp(arg, "--error-unmatch")) { - error_unmatch = 1; - continue; - } - if (!prefixcmp(arg, "--with-tree=")) { - with_tree = arg + 12; - continue; - } - if (!prefixcmp(arg, "--abbrev=")) { - abbrev = strtoul(arg+9, NULL, 10); - if (abbrev && abbrev < MINIMUM_ABBREV) - abbrev = MINIMUM_ABBREV; - else if (abbrev > 40) - abbrev = 40; - continue; - } - if (!strcmp(arg, "--abbrev")) { - abbrev = DEFAULT_ABBREV; - continue; - } - if (*arg == '-') - usage(ls_files_usage); - break; + argc = parse_options(argc, argv, prefix, builtin_ls_files_options, + ls_files_usage, 0); + if (show_tag || show_valid_bit) { + tag_cached = "H "; + tag_unmerged = "M "; + tag_removed = "R "; + tag_modified = "C "; + tag_other = "? "; + tag_killed = "K "; } + if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed) + require_work_tree = 1; + if (show_unmerged) + /* + * There's no point in showing unmerged unless + * you also show the stage information. + */ + show_stage = 1; + if (dir.exclude_per_dir) + exc_given = 1; if (require_work_tree && !is_inside_work_tree()) setup_work_tree(); - pathspec = get_pathspec(prefix, argv + i); + pathspec = get_pathspec(prefix, argv); - /* be nice with submodule patsh ending in a slash */ + /* be nice with submodule paths ending in a slash */ read_cache(); if (pathspec) strip_trailing_slash_from_submodules(); @@ -543,7 +529,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix) ps_matched = xcalloc(1, num); } - if (dir.show_ignored && !exc_given) { + if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) { fprintf(stderr, "%s: --ignored needs some exclude pattern\n", argv[0]); exit(1); diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index fca46312f6..22008dfa8f 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -60,7 +60,6 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, { int retval = 0; const char *type = blob_type; - unsigned long size; if (S_ISGITLINK(mode)) { /* @@ -90,17 +89,20 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen, if (!(ls_options & LS_NAME_ONLY)) { if (ls_options & LS_SHOW_SIZE) { + char size_text[24]; if (!strcmp(type, blob_type)) { - sha1_object_info(sha1, &size); - printf("%06o %s %s %7lu\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1), - size); + unsigned long size; + if (sha1_object_info(sha1, &size) == OBJ_BAD) + strcpy(size_text, "BAD"); + else + snprintf(size_text, sizeof(size_text), + "%lu", size); } else - printf("%06o %s %s %7c\t", mode, type, - abbrev ? find_unique_abbrev(sha1, abbrev) - : sha1_to_hex(sha1), - '-'); + strcpy(size_text, "-"); + printf("%06o %s %s %7s\t", mode, type, + abbrev ? find_unique_abbrev(sha1, abbrev) + : sha1_to_hex(sha1), + size_text); } else printf("%06o %s %s\t", mode, type, abbrev ? find_unique_abbrev(sha1, abbrev) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 2789ccdf7d..92637ac0ba 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -193,8 +193,7 @@ static void handle_content_type(struct strbuf *line) *content_top = boundary; boundary = NULL; } - if (slurp_attr(line->buf, "charset=", &charset)) - strbuf_tolower(&charset); + slurp_attr(line->buf, "charset=", &charset); if (boundary) { strbuf_release(boundary); @@ -481,7 +480,7 @@ static const char *guess_charset(const struct strbuf *line, const char *target_c if (is_utf8(line->buf)) return NULL; } - return "latin1"; + return "ISO8859-1"; } static void convert_to_utf8(struct strbuf *line, const char *charset) @@ -494,7 +493,7 @@ static void convert_to_utf8(struct strbuf *line, const char *charset) return; } - if (!strcmp(metainfo_charset, charset)) + if (!strcasecmp(metainfo_charset, charset)) return; out = reencode_string(line->buf, metainfo_charset, charset); if (!out) @@ -537,7 +536,6 @@ static int decode_header_bq(struct strbuf *it) */ strbuf_add(&outbuf, in, ep - in); } - in = ep; } /* E.g. * ep : "=?iso-2022-jp?B?GyR...?= foo" @@ -551,7 +549,6 @@ static int decode_header_bq(struct strbuf *it) if (cp + 3 - it->buf > it->len) goto decode_header_bq_out; strbuf_add(&charset_q, ep, cp - ep); - strbuf_tolower(&charset_q); encoding = cp[1]; if (!encoding || cp[2] != '?') @@ -945,7 +942,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) */ git_config(git_default_config, NULL); - def_charset = (git_commit_encoding ? git_commit_encoding : "utf-8"); + def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8"); metainfo_charset = def_charset; while (1 < argc && argv[1][0] == '-') { diff --git a/builtin-merge-base.c b/builtin-merge-base.c index 03fc1c2114..a6ec2f7ab7 100644 --- a/builtin-merge-base.c +++ b/builtin-merge-base.c @@ -53,7 +53,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) }; git_config(git_default_config, NULL); - argc = parse_options(argc, argv, options, merge_base_usage, 0); + argc = parse_options(argc, argv, prefix, options, merge_base_usage, 0); if (argc < 2) usage_with_options(merge_base_usage, options); rev = xmalloc(argc * sizeof(*rev)); diff --git a/builtin-merge-file.c b/builtin-merge-file.c index 96edb97a83..afd2ea7a73 100644 --- a/builtin-merge-file.c +++ b/builtin-merge-file.c @@ -48,7 +48,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) merge_style = git_xmerge_style; } - argc = parse_options(argc, argv, options, merge_file_usage, 0); + argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); if (argc != 3) usage_with_options(merge_file_usage, options); if (quiet) { diff --git a/builtin-merge-recursive.c b/builtin-merge-recursive.c index 703045bfc8..d26a96e486 100644 --- a/builtin-merge-recursive.c +++ b/builtin-merge-recursive.c @@ -45,8 +45,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) bases[bases_count++] = sha; } else - warning("Cannot handle more than %zu bases. " - "Ignoring %s.", ARRAY_SIZE(bases)-1, argv[i]); + warning("Cannot handle more than %d bases. " + "Ignoring %s.", + (int)ARRAY_SIZE(bases)-1, argv[i]); } if (argc - i != 3) /* "--" "<head>" "<remote>" */ die("Not handling anything other than two heads merge."); diff --git a/builtin-merge.c b/builtin-merge.c index c339380cf3..793f2f4a18 100644 --- a/builtin-merge.c +++ b/builtin-merge.c @@ -360,9 +360,8 @@ static void merge_name(const char *remote, struct strbuf *msg) const char *ptr; int len, early; - len = strlen(remote); - if (interpret_nth_last_branch(remote, &bname) == len) - remote = bname.buf; + strbuf_branchname(&bname, remote); + remote = bname.buf; memset(branch_head, 0, sizeof(branch_head)); remote_head = peel_to_type(remote, 0, NULL, OBJ_COMMIT); @@ -463,7 +462,7 @@ static int git_merge_config(const char *k, const char *v, void *cb) argv = xrealloc(argv, sizeof(*argv) * (argc + 2)); memmove(argv + 1, argv, sizeof(*argv) * (argc + 1)); argc++; - parse_options(argc, argv, builtin_merge_options, + parse_options(argc, argv, NULL, builtin_merge_options, builtin_merge_usage, 0); free(buf); } @@ -636,7 +635,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote) memset(&opts, 0, sizeof(opts)); memset(&t, 0, sizeof(t)); memset(&dir, 0, sizeof(dir)); - dir.show_ignored = 1; + dir.flags |= DIR_SHOW_IGNORED; dir.exclude_per_dir = ".gitignore"; opts.dir = &dir; @@ -837,8 +836,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) struct commit_list **remotes = &remoteheads; setup_work_tree(); + if (file_exists(git_path("MERGE_HEAD"))) + die("You have not concluded your merge. (MERGE_HEAD exists)"); if (read_cache_unmerged()) - die("You are in the middle of a conflicted merge."); + die("You are in the middle of a conflicted merge." + " (index unmerged)"); /* * Check if we are _not_ on a detached HEAD, i.e. if there is a @@ -856,7 +858,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (diff_use_color_default == -1) diff_use_color_default = git_use_color_default; - argc = parse_options(argc, argv, builtin_merge_options, + argc = parse_options(argc, argv, prefix, builtin_merge_options, builtin_merge_usage, 0); if (verbosity < 0) show_diffstat = 0; diff --git a/builtin-mktree.c b/builtin-mktree.c new file mode 100644 index 0000000000..098395fda1 --- /dev/null +++ b/builtin-mktree.c @@ -0,0 +1,190 @@ +/* + * GIT - the stupid content tracker + * + * Copyright (c) Junio C Hamano, 2006, 2009 + */ +#include "builtin.h" +#include "quote.h" +#include "tree.h" +#include "parse-options.h" + +static struct treeent { + unsigned mode; + unsigned char sha1[20]; + int len; + char name[FLEX_ARRAY]; +} **entries; +static int alloc, used; + +static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) +{ + struct treeent *ent; + int len = strlen(path); + if (strchr(path, '/')) + die("path %s contains slash", path); + + if (alloc <= used) { + alloc = alloc_nr(used); + entries = xrealloc(entries, sizeof(*entries) * alloc); + } + ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); + ent->mode = mode; + ent->len = len; + hashcpy(ent->sha1, sha1); + memcpy(ent->name, path, len+1); +} + +static int ent_compare(const void *a_, const void *b_) +{ + struct treeent *a = *(struct treeent **)a_; + struct treeent *b = *(struct treeent **)b_; + return base_name_compare(a->name, a->len, a->mode, + b->name, b->len, b->mode); +} + +static void write_tree(unsigned char *sha1) +{ + struct strbuf buf; + size_t size; + int i; + + qsort(entries, used, sizeof(*entries), ent_compare); + for (size = i = 0; i < used; i++) + size += 32 + entries[i]->len; + + strbuf_init(&buf, size); + for (i = 0; i < used; i++) { + struct treeent *ent = entries[i]; + strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0'); + strbuf_add(&buf, ent->sha1, 20); + } + + write_sha1_file(buf.buf, buf.len, tree_type, sha1); +} + +static const char *mktree_usage[] = { + "git mktree [-z] [--missing] [--batch]", + NULL +}; + +static void mktree_line(char *buf, size_t len, int line_termination, int allow_missing) +{ + char *ptr, *ntr; + unsigned mode; + enum object_type mode_type; /* object type derived from mode */ + enum object_type obj_type; /* object type derived from sha */ + char *path; + unsigned char sha1[20]; + + ptr = buf; + /* + * Read non-recursive ls-tree output format: + * mode SP type SP sha1 TAB name + */ + mode = strtoul(ptr, &ntr, 8); + if (ptr == ntr || !ntr || *ntr != ' ') + die("input format error: %s", buf); + ptr = ntr + 1; /* type */ + ntr = strchr(ptr, ' '); + if (!ntr || buf + len <= ntr + 40 || + ntr[41] != '\t' || + get_sha1_hex(ntr + 1, sha1)) + die("input format error: %s", buf); + + /* It is perfectly normal if we do not have a commit from a submodule */ + if (S_ISGITLINK(mode)) + allow_missing = 1; + + + *ntr++ = 0; /* now at the beginning of SHA1 */ + + path = ntr + 41; /* at the beginning of name */ + if (line_termination && path[0] == '"') { + struct strbuf p_uq = STRBUF_INIT; + if (unquote_c_style(&p_uq, path, NULL)) + die("invalid quoting"); + path = strbuf_detach(&p_uq, NULL); + } + + /* + * Object type is redundantly derivable three ways. + * These should all agree. + */ + mode_type = object_type(mode); + if (mode_type != type_from_string(ptr)) { + die("entry '%s' object type (%s) doesn't match mode type (%s)", + path, ptr, typename(mode_type)); + } + + /* Check the type of object identified by sha1 */ + obj_type = sha1_object_info(sha1, NULL); + if (obj_type < 0) { + if (allow_missing) { + ; /* no problem - missing objects are presumed to be of the right type */ + } else { + die("entry '%s' object %s is unavailable", path, sha1_to_hex(sha1)); + } + } else { + if (obj_type != mode_type) { + /* + * The object exists but is of the wrong type. + * This is a problem regardless of allow_missing + * because the new tree entry will never be correct. + */ + die("entry '%s' object %s is a %s but specified type was (%s)", + path, sha1_to_hex(sha1), typename(obj_type), typename(mode_type)); + } + } + + append_to_tree(mode, sha1, path); +} + +int cmd_mktree(int ac, const char **av, const char *prefix) +{ + struct strbuf sb = STRBUF_INIT; + unsigned char sha1[20]; + int line_termination = '\n'; + int allow_missing = 0; + int is_batch_mode = 0; + int got_eof = 0; + + const struct option option[] = { + OPT_SET_INT('z', NULL, &line_termination, "input is NUL terminated", '\0'), + OPT_SET_INT( 0 , "missing", &allow_missing, "allow missing objects", 1), + OPT_SET_INT( 0 , "batch", &is_batch_mode, "allow creation of more than one tree", 1), + OPT_END() + }; + + ac = parse_options(ac, av, prefix, option, mktree_usage, 0); + + while (!got_eof) { + while (1) { + if (strbuf_getline(&sb, stdin, line_termination) == EOF) { + got_eof = 1; + break; + } + if (sb.buf[0] == '\0') { + /* empty lines denote tree boundaries in batch mode */ + if (is_batch_mode) + break; + die("input format error: (blank line only valid in batch mode)"); + } + mktree_line(sb.buf, sb.len, line_termination, allow_missing); + } + if (is_batch_mode && got_eof && used < 1) { + /* + * Execution gets here if the last tree entry is terminated with a + * new-line. The final new-line has been made optional to be + * consistent with the original non-batch behaviour of mktree. + */ + ; /* skip creating an empty tree */ + } else { + write_tree(sha1); + puts(sha1_to_hex(sha1)); + fflush(stdout); + } + used=0; /* reset tree entry buffer for re-use in batch mode */ + } + strbuf_release(&sb); + exit(0); +} diff --git a/builtin-mv.c b/builtin-mv.c index 01270fefdf..8b81d4b51d 100644 --- a/builtin-mv.c +++ b/builtin-mv.c @@ -72,7 +72,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die("index file corrupt"); - argc = parse_options(argc, argv, builtin_mv_options, builtin_mv_usage, 0); + argc = parse_options(argc, argv, prefix, builtin_mv_options, + builtin_mv_usage, 0); if (--argc < 1) usage_with_options(builtin_mv_usage, builtin_mv_options); diff --git a/builtin-name-rev.c b/builtin-name-rev.c index 08c8aabf94..06a38ac8c1 100644 --- a/builtin-name-rev.c +++ b/builtin-name-rev.c @@ -238,7 +238,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) }; git_config(git_default_config, NULL); - argc = parse_options(argc, argv, opts, name_rev_usage, 0); + argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); if (!!all + !!transform_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index b859cb147c..941cc2d73c 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -293,7 +293,7 @@ static unsigned long write_object(struct sha1file *f, die("unable to read %s", sha1_to_hex(entry->idx.sha1)); /* * make sure no cached delta data remains from a - * previous attempt before a pack split occured. + * previous attempt before a pack split occurred. */ free(entry->delta_data); entry->delta_data = NULL; @@ -653,8 +653,7 @@ static void rehash_objects(void) static unsigned name_hash(const char *name) { - unsigned char c; - unsigned hash = 0; + unsigned c, hash = 0; if (!name) return 0; @@ -1612,7 +1611,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, return; } if (progress > pack_to_stdout) - fprintf(stderr, "Delta compression using %d threads.\n", + fprintf(stderr, "Delta compression using up to %d threads.\n", delta_search_threads); /* Partition the work amongst work threads. */ @@ -1901,7 +1900,7 @@ static void read_object_list_from_stdin(void) #define OBJECT_ADDED (1u<<20) -static void show_commit(struct commit *commit) +static void show_commit(struct commit *commit, void *data) { add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0); commit->object.flags |= OBJECT_ADDED; @@ -2079,7 +2078,7 @@ static void get_object_list(int ac, const char **av) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); + traverse_commit_list(&revs, show_commit, show_object, NULL); if (keep_unreachable) add_objects_in_unpacked_packs(&revs); diff --git a/builtin-pack-refs.c b/builtin-pack-refs.c index 34246df4ec..091860b2e3 100644 --- a/builtin-pack-refs.c +++ b/builtin-pack-refs.c @@ -15,7 +15,7 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix) OPT_BIT(0, "prune", &flags, "prune loose refs (default)", PACK_REFS_PRUNE), OPT_END(), }; - if (parse_options(argc, argv, opts, pack_refs_usage, 0)) + if (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0)) usage_with_options(pack_refs_usage, opts); return pack_refs(flags); } diff --git a/builtin-prune-packed.c b/builtin-prune-packed.c index 2d5b2cd353..00590b1c3c 100644 --- a/builtin-prune-packed.c +++ b/builtin-prune-packed.c @@ -28,8 +28,8 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts) memcpy(pathname + len, de->d_name, 38); if (opts & DRY_RUN) printf("rm -f %s\n", pathname); - else if (unlink(pathname) < 0) - error("unable to unlink %s", pathname); + else + unlink_or_warn(pathname); display_progress(progress, i + 1); } pathname[len] = 0; @@ -55,6 +55,7 @@ void prune_packed_objects(int opts) for (i = 0; i < 256; i++) { DIR *d; + display_progress(progress, i + 1); sprintf(pathname + len, "%02x/", i); d = opendir(pathname); if (!d) diff --git a/builtin-prune.c b/builtin-prune.c index 545e9c1f94..0ed9cce4a2 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -27,7 +27,7 @@ static int prune_tmp_object(const char *path, const char *filename) } printf("Removing stale temporary file %s\n", fullpath); if (!show_only) - unlink(fullpath); + unlink_or_warn(fullpath); return 0; } @@ -47,7 +47,7 @@ static int prune_object(char *path, const char *filename, const unsigned char *s (type > 0) ? typename(type) : "unknown"); } if (!show_only) - unlink(fullpath); + unlink_or_warn(fullpath); return 0; } @@ -142,7 +142,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; init_revisions(&revs, prefix); - argc = parse_options(argc, argv, options, prune_usage, 0); + argc = parse_options(argc, argv, prefix, options, prune_usage, 0); while (argc--) { unsigned char sha1[20]; const char *name = *argv++; diff --git a/builtin-push.c b/builtin-push.c index ca36fb1e58..c869974013 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -48,6 +48,71 @@ static void set_refspecs(const char **refs, int nr) } } +static void setup_push_tracking(void) +{ + struct strbuf refspec = STRBUF_INIT; + struct branch *branch = branch_get(NULL); + if (!branch) + die("You are not currently on a branch."); + if (!branch->merge_nr) + die("The current branch %s is not tracking anything.", + branch->name); + if (branch->merge_nr != 1) + die("The current branch %s is tracking multiple branches, " + "refusing to push.", branch->name); + strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src); + add_refspec(refspec.buf); +} + +static const char *warn_unconfigured_push_msg[] = { + "You did not specify any refspecs to push, and the current remote", + "has not configured any push refspecs. The default action in this", + "case is to push all matching refspecs, that is, all branches", + "that exist both locally and remotely will be updated. This may", + "not necessarily be what you want to happen.", + "", + "You can specify what action you want to take in this case, and", + "avoid seeing this message again, by configuring 'push.default' to:", + " 'nothing' : Do not push anything", + " 'matching' : Push all matching branches (default)", + " 'tracking' : Push the current branch to whatever it is tracking", + " 'current' : Push the current branch" +}; + +static void warn_unconfigured_push(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(warn_unconfigured_push_msg); i++) + warning("%s", warn_unconfigured_push_msg[i]); +} + +static void setup_default_push_refspecs(void) +{ + git_config(git_default_config, NULL); + switch (push_default) { + case PUSH_DEFAULT_UNSPECIFIED: + warn_unconfigured_push(); + /* fallthrough */ + + case PUSH_DEFAULT_MATCHING: + add_refspec(":"); + break; + + case PUSH_DEFAULT_TRACKING: + setup_push_tracking(); + break; + + case PUSH_DEFAULT_CURRENT: + add_refspec("HEAD"); + break; + + case PUSH_DEFAULT_NOTHING: + die("You didn't specify any refspecs to push, and " + "push.default is \"nothing\"."); + break; + } +} + static int do_push(const char *repo, int flags) { int i, errs; @@ -79,11 +144,12 @@ static int do_push(const char *repo, int flags) return error("--all and --mirror are incompatible"); } - if (!refspec - && !(flags & TRANSPORT_PUSH_ALL) - && remote->push_refspec_nr) { - refspec = remote->push_refspec; - refspec_nr = remote->push_refspec_nr; + if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) { + if (remote->push_refspec_nr) { + refspec = remote->push_refspec; + refspec_nr = remote->push_refspec_nr; + } else if (!(flags & TRANSPORT_PUSH_MIRROR)) + setup_default_push_refspecs(); } errs = 0; for (i = 0; i < remote->url_nr; i++) { @@ -132,7 +198,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_END() }; - argc = parse_options(argc, argv, options, push_usage, 0); + argc = parse_options(argc, argv, prefix, options, push_usage, 0); if (tags) add_refspec("refs/tags/*"); diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 391d709704..82e25eaa07 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -135,7 +135,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("more than one --exclude-per-directory are given."); dir = xcalloc(1, sizeof(*opts.dir)); - dir->show_ignored = 1; + dir->flags |= DIR_SHOW_IGNORED; dir->exclude_per_dir = arg + 24; opts.dir = dir; /* We do not need to nor want to do read-directory diff --git a/builtin-receive-pack.c b/builtin-receive-pack.c index 849f1fe6f9..33d345dc39 100644 --- a/builtin-receive-pack.c +++ b/builtin-receive-pack.c @@ -27,10 +27,9 @@ static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int unpack_limit = 100; static int report_status; +static int prefer_ofs_delta = 1; static const char *head_name; - -static char capabilities[] = " report-status delete-refs "; -static int capabilities_sent; +static char *capabilities_to_send; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -84,24 +83,29 @@ static int receive_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); } static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data) { - if (capabilities_sent) + if (!capabilities_to_send) packet_write(1, "%s %s\n", sha1_to_hex(sha1), path); else packet_write(1, "%s %s%c%s\n", - sha1_to_hex(sha1), path, 0, capabilities); - capabilities_sent = 1; + sha1_to_hex(sha1), path, 0, capabilities_to_send); + capabilities_to_send = NULL; return 0; } static void write_head_info(void) { for_each_ref(show_ref, NULL); - if (!capabilities_sent) + if (capabilities_to_send) show_ref("capabilities^{}", null_sha1, 0, NULL); } @@ -188,7 +192,6 @@ static int run_receive_hook(const char *hook_name) static int run_update_hook(struct command *cmd) { static const char update_hook[] = "hooks/update"; - struct child_process proc; const char *argv[5]; if (access(update_hook, X_OK) < 0) @@ -200,12 +203,9 @@ static int run_update_hook(struct command *cmd) argv[3] = sha1_to_hex(cmd->new_sha1); argv[4] = NULL; - memset(&proc, 0, sizeof(proc)); - proc.argv = argv; - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - - return hook_status(run_command(&proc), update_hook); + return hook_status(run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | + RUN_COMMAND_STDOUT_TO_STDERR), + update_hook); } static int is_ref_checked_out(const char *ref) @@ -675,7 +675,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) setup_path(); if (!enter_repo(dir, 0)) - die("'%s': unable to chdir or not a git archive", dir); + die("'%s' does not appear to be a git repository", dir); if (is_repository_shallow()) die("attempt to push into a shallow repository"); @@ -687,6 +687,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) else if (0 <= receive_unpack_limit) unpack_limit = receive_unpack_limit; + capabilities_to_send = (prefer_ofs_delta) ? + " report-status delete-refs ofs-delta " : + " report-status delete-refs "; + add_alternate_refs(); write_head_info(); clear_extra_refs(); @@ -702,7 +706,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) unpack_status = unpack(); execute_commands(unpack_status); if (pack_lockfile) - unlink(pack_lockfile); + unlink_or_warn(pack_lockfile); if (report_status) report(unpack_status); run_receive_hook(post_receive_hook); diff --git a/builtin-reflog.c b/builtin-reflog.c index 249ad2a311..ddfdf5a3cb 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -240,10 +240,10 @@ static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsig static void mark_reachable(struct commit *commit, unsigned long expire_limit) { /* - * We need to compute if commit on either side of an reflog + * We need to compute whether the commit on either side of a 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 + * time threshold first; we know a commit marked thusly is * reachable from the tip without running in_merge_bases() * at all. */ diff --git a/builtin-remote.c b/builtin-remote.c index ac69d37c8a..dfc0b9e706 100644 --- a/builtin-remote.c +++ b/builtin-remote.c @@ -12,15 +12,21 @@ static const char * const builtin_remote_usage[] = { "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>", "git remote rename <old> <new>", "git remote rm <name>", + "git remote set-head <name> [-a | -d | <branch>]", "git remote show [-n] <name>", "git remote prune [-n | --dry-run] <name>", - "git remote [-v | --verbose] update [group]", + "git remote [-v | --verbose] update [-p | --prune] [group]", NULL }; +#define GET_REF_STATES (1<<0) +#define GET_HEAD_NAMES (1<<1) +#define GET_PUSH_REF_STATES (1<<2) + static int verbose; static int show_all(void); +static int prune_remote(const char *remote, int dry_run); static inline int postfixcmp(const char *string, const char *postfix) { @@ -73,7 +79,8 @@ static int add(int argc, const char **argv) OPT_END() }; - argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + argc = parse_options(argc, argv, NULL, options, builtin_remote_usage, + 0); if (argc < 2) usage_with_options(builtin_remote_usage, options); @@ -143,8 +150,9 @@ static int add(int argc, const char **argv) } struct branch_info { - char *remote; + char *remote_name; struct string_list merge; + int rebase; }; static struct string_list branch_list; @@ -161,10 +169,11 @@ static const char *abbrev_ref(const char *name, const char *prefix) static int config_read_branches(const char *key, const char *value, void *cb) { if (!prefixcmp(key, "branch.")) { + const char *orig_key = key; char *name; struct string_list_item *item; struct branch_info *info; - enum { REMOTE, MERGE } type; + enum { REMOTE, MERGE, REBASE } type; key += 7; if (!postfixcmp(key, ".remote")) { @@ -173,6 +182,9 @@ static int config_read_branches(const char *key, const char *value, void *cb) } else if (!postfixcmp(key, ".merge")) { name = xstrndup(key, strlen(key) - 6); type = MERGE; + } else if (!postfixcmp(key, ".rebase")) { + name = xstrndup(key, strlen(key) - 7); + type = REBASE; } else return 0; @@ -182,10 +194,10 @@ static int config_read_branches(const char *key, const char *value, void *cb) item->util = xcalloc(sizeof(struct branch_info), 1); info = item->util; if (type == REMOTE) { - if (info->remote) - warning("more than one branch.%s", key); - info->remote = xstrdup(value); - } else { + if (info->remote_name) + warning("more than one %s", orig_key); + info->remote_name = xstrdup(value); + } else if (type == MERGE) { char *space = strchr(value, ' '); value = abbrev_branch(value); while (space) { @@ -196,7 +208,8 @@ static int config_read_branches(const char *key, const char *value, void *cb) space = strchr(value, ' '); } string_list_append(xstrdup(value), &info->merge); - } + } else + info->rebase = git_config_bool(orig_key, value); } return 0; } @@ -206,12 +219,12 @@ static void read_branches(void) if (branch_list.nr) return; git_config(config_read_branches, NULL); - sort_string_list(&branch_list); } struct ref_states { struct remote *remote; - struct string_list new, stale, tracked; + struct string_list new, stale, tracked, heads, push; + int queried; }; static int handle_one_branch(const char *refname, @@ -227,10 +240,8 @@ static int handle_one_branch(const char *refname, const char *name = abbrev_branch(refspec.src); /* symbolic refs pointing nowhere were handled already */ if ((flags & REF_ISSYMREF) || - unsorted_string_list_has_string(&states->tracked, - name) || - unsorted_string_list_has_string(&states->new, - name)) + string_list_has_string(&states->tracked, name) || + string_list_has_string(&states->new, name)) return 0; item = string_list_append(name, &states->stale); item->util = xstrdup(refname); @@ -238,39 +249,151 @@ static int handle_one_branch(const char *refname, return 0; } -static int get_ref_states(const struct ref *ref, struct ref_states *states) +static int get_ref_states(const struct ref *remote_refs, struct ref_states *states) { struct ref *fetch_map = NULL, **tail = &fetch_map; + struct ref *ref; int i; for (i = 0; i < states->remote->fetch_refspec_nr; i++) - if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1)) + if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1)) die("Could not get fetch map for refspec %s", states->remote->fetch_refspec[i]); states->new.strdup_strings = states->tracked.strdup_strings = 1; for (ref = fetch_map; ref; ref = ref->next) { - struct string_list *target = &states->tracked; unsigned char sha1[20]; - void *util = NULL; - if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1)) - target = &states->new; - else { - target = &states->tracked; - if (hashcmp(sha1, ref->new_sha1)) - util = &states; - } - string_list_append(abbrev_branch(ref->name), target)->util = util; + string_list_append(abbrev_branch(ref->name), &states->new); + else + string_list_append(abbrev_branch(ref->name), &states->tracked); } free_refs(fetch_map); + sort_string_list(&states->new); + sort_string_list(&states->tracked); for_each_ref(handle_one_branch, states); sort_string_list(&states->stale); return 0; } +struct push_info { + char *dest; + int forced; + enum { + PUSH_STATUS_CREATE = 0, + PUSH_STATUS_DELETE, + PUSH_STATUS_UPTODATE, + PUSH_STATUS_FASTFORWARD, + PUSH_STATUS_OUTOFDATE, + PUSH_STATUS_NOTQUERIED, + } status; +}; + +static int get_push_ref_states(const struct ref *remote_refs, + struct ref_states *states) +{ + struct remote *remote = states->remote; + struct ref *ref, *local_refs, *push_map; + if (remote->mirror) + return 0; + + local_refs = get_local_heads(); + push_map = copy_ref_list(remote_refs); + + match_refs(local_refs, &push_map, remote->push_refspec_nr, + remote->push_refspec, MATCH_REFS_NONE); + + states->push.strdup_strings = 1; + for (ref = push_map; ref; ref = ref->next) { + struct string_list_item *item; + struct push_info *info; + + if (!ref->peer_ref) + continue; + hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); + + item = string_list_append(abbrev_branch(ref->peer_ref->name), + &states->push); + item->util = xcalloc(sizeof(struct push_info), 1); + info = item->util; + info->forced = ref->force; + info->dest = xstrdup(abbrev_branch(ref->name)); + + if (is_null_sha1(ref->new_sha1)) { + info->status = PUSH_STATUS_DELETE; + } else if (!hashcmp(ref->old_sha1, ref->new_sha1)) + info->status = PUSH_STATUS_UPTODATE; + else if (is_null_sha1(ref->old_sha1)) + info->status = PUSH_STATUS_CREATE; + else if (has_sha1_file(ref->old_sha1) && + ref_newer(ref->new_sha1, ref->old_sha1)) + info->status = PUSH_STATUS_FASTFORWARD; + else + info->status = PUSH_STATUS_OUTOFDATE; + } + free_refs(local_refs); + free_refs(push_map); + return 0; +} + +static int get_push_ref_states_noquery(struct ref_states *states) +{ + int i; + struct remote *remote = states->remote; + struct string_list_item *item; + struct push_info *info; + + if (remote->mirror) + return 0; + + states->push.strdup_strings = 1; + if (!remote->push_refspec_nr) { + item = string_list_append("(matching)", &states->push); + info = item->util = xcalloc(sizeof(struct push_info), 1); + info->status = PUSH_STATUS_NOTQUERIED; + info->dest = xstrdup(item->string); + } + for (i = 0; i < remote->push_refspec_nr; i++) { + struct refspec *spec = remote->push + i; + if (spec->matching) + item = string_list_append("(matching)", &states->push); + else if (strlen(spec->src)) + item = string_list_append(spec->src, &states->push); + else + item = string_list_append("(delete)", &states->push); + + info = item->util = xcalloc(sizeof(struct push_info), 1); + info->forced = spec->force; + info->status = PUSH_STATUS_NOTQUERIED; + info->dest = xstrdup(spec->dst ? spec->dst : item->string); + } + return 0; +} + +static int get_head_names(const struct ref *remote_refs, struct ref_states *states) +{ + struct ref *ref, *matches; + struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map; + struct refspec refspec; + + refspec.force = 0; + refspec.pattern = 1; + refspec.src = refspec.dst = "refs/heads/*"; + states->heads.strdup_strings = 1; + get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0); + matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"), + fetch_map, 1); + for(ref = matches; ref; ref = ref->next) + string_list_append(abbrev_branch(ref->name), &states->heads); + + free_refs(fetch_map); + free_refs(matches); + + return 0; +} + struct known_remote { struct known_remote *next; struct remote *remote; @@ -400,8 +523,8 @@ static int migrate_file(struct remote *remote) path = git_path("remotes/%s", remote->name); else if (remote->origin == REMOTE_BRANCHES) path = git_path("branches/%s", remote->name); - if (path && unlink(path)) - warning("failed to remove '%s'", path); + if (path) + unlink_or_warn(path); return 0; } @@ -466,7 +589,7 @@ static int mv(int argc, const char **argv) for (i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; - if (info->remote && !strcmp(info->remote, rename.old)) { + if (info->remote_name && !strcmp(info->remote_name, rename.old)) { strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.remote", item->string); if (git_config_set(buf.buf, rename.new)) { @@ -484,9 +607,8 @@ static int mv(int argc, const char **argv) struct string_list_item *item = remote_branches.items + i; int flag = 0; unsigned char sha1[20]; - const char *symref; - symref = resolve_ref(item->string, sha1, 1, &flag); + resolve_ref(item->string, sha1, 1, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(item->string, NULL, REF_NODEREF)) @@ -576,7 +698,7 @@ static int rm(int argc, const char **argv) for (i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; - if (info->remote && !strcmp(info->remote, remote->name)) { + if (info->remote_name && !strcmp(info->remote_name, remote->name)) { const char *keys[] = { "remote", "merge", NULL }, **k; for (k = keys; *k; k++) { strbuf_reset(&buf); @@ -618,18 +740,37 @@ static int rm(int argc, const char **argv) return result; } -static void show_list(const char *title, struct string_list *list, - const char *extra_arg) +void clear_push_info(void *util, const char *string) { - int i; + struct push_info *info = util; + free(info->dest); + free(info); +} - if (!list->nr) - return; +static void free_remote_ref_states(struct ref_states *states) +{ + string_list_clear(&states->new, 0); + string_list_clear(&states->stale, 0); + string_list_clear(&states->tracked, 0); + string_list_clear(&states->heads, 0); + string_list_clear_func(&states->push, clear_push_info); +} - printf(title, list->nr > 1 ? "es" : "", extra_arg); - printf("\n"); - for (i = 0; i < list->nr; i++) - printf(" %s\n", list->items[i].string); +static int append_ref_to_tracked_list(const char *refname, + const unsigned char *sha1, int flags, void *cb_data) +{ + struct ref_states *states = cb_data; + struct refspec refspec; + + if (flags & REF_ISSYMREF) + return 0; + + memset(&refspec, 0, sizeof(refspec)); + refspec.dst = (char *)refname; + if (!remote_find_tracking(states->remote, &refspec)) + string_list_append(abbrev_branch(refspec.src), &states->tracked); + + return 0; } static int get_remote_ref_states(const char *name, @@ -637,7 +778,7 @@ static int get_remote_ref_states(const char *name, int query) { struct transport *transport; - const struct ref *ref; + const struct ref *remote_refs; states->remote = remote_get(name); if (!states->remote) @@ -648,102 +789,338 @@ static int get_remote_ref_states(const char *name, if (query) { transport = transport_get(NULL, states->remote->url_nr > 0 ? states->remote->url[0] : NULL); - ref = transport_get_remote_refs(transport); + remote_refs = transport_get_remote_refs(transport); transport_disconnect(transport); - get_ref_states(ref, states); + states->queried = 1; + if (query & GET_REF_STATES) + get_ref_states(remote_refs, states); + if (query & GET_HEAD_NAMES) + get_head_names(remote_refs, states); + if (query & GET_PUSH_REF_STATES) + get_push_ref_states(remote_refs, states); + } else { + for_each_ref(append_ref_to_tracked_list, states); + sort_string_list(&states->tracked); + get_push_ref_states_noquery(states); } return 0; } -static int append_ref_to_tracked_list(const char *refname, - const unsigned char *sha1, int flags, void *cb_data) +struct show_info { + struct string_list *list; + struct ref_states *states; + int width, width2; + int any_rebase; +}; + +int add_remote_to_show_info(struct string_list_item *item, void *cb_data) { - struct ref_states *states = cb_data; - struct refspec refspec; + struct show_info *info = cb_data; + int n = strlen(item->string); + if (n > info->width) + info->width = n; + string_list_insert(item->string, info->list); + return 0; +} - memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; - if (!remote_find_tracking(states->remote, &refspec)) - string_list_append(abbrev_branch(refspec.src), &states->tracked); +int show_remote_info_item(struct string_list_item *item, void *cb_data) +{ + struct show_info *info = cb_data; + struct ref_states *states = info->states; + const char *name = item->string; + + if (states->queried) { + const char *fmt = "%s"; + const char *arg = ""; + if (string_list_has_string(&states->new, name)) { + fmt = " new (next fetch will store in remotes/%s)"; + arg = states->remote->name; + } else if (string_list_has_string(&states->tracked, name)) + arg = " tracked"; + else if (string_list_has_string(&states->stale, name)) + arg = " stale (use 'git remote prune' to remove)"; + else + arg = " ???"; + printf(" %-*s", info->width, name); + printf(fmt, arg); + printf("\n"); + } else + printf(" %s\n", name); return 0; } +int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data) +{ + struct show_info *show_info = cb_data; + struct ref_states *states = show_info->states; + struct branch_info *branch_info = branch_item->util; + struct string_list_item *item; + int n; + + if (!branch_info->merge.nr || !branch_info->remote_name || + strcmp(states->remote->name, branch_info->remote_name)) + return 0; + if ((n = strlen(branch_item->string)) > show_info->width) + show_info->width = n; + if (branch_info->rebase) + show_info->any_rebase = 1; + + item = string_list_insert(branch_item->string, show_info->list); + item->util = branch_info; + + return 0; +} + +int show_local_info_item(struct string_list_item *item, void *cb_data) +{ + struct show_info *show_info = cb_data; + struct branch_info *branch_info = item->util; + struct string_list *merge = &branch_info->merge; + const char *also; + int i; + + if (branch_info->rebase && branch_info->merge.nr > 1) { + error("invalid branch.%s.merge; cannot rebase onto > 1 branch", + item->string); + return 0; + } + + printf(" %-*s ", show_info->width, item->string); + if (branch_info->rebase) { + printf("rebases onto remote %s\n", merge->items[0].string); + return 0; + } else if (show_info->any_rebase) { + printf(" merges with remote %s\n", merge->items[0].string); + also = " and with remote"; + } else { + printf("merges with remote %s\n", merge->items[0].string); + also = " and with remote"; + } + for (i = 1; i < merge->nr; i++) + printf(" %-*s %s %s\n", show_info->width, "", also, + merge->items[i].string); + + return 0; +} + +int add_push_to_show_info(struct string_list_item *push_item, void *cb_data) +{ + struct show_info *show_info = cb_data; + struct push_info *push_info = push_item->util; + struct string_list_item *item; + int n; + if ((n = strlen(push_item->string)) > show_info->width) + show_info->width = n; + if ((n = strlen(push_info->dest)) > show_info->width2) + show_info->width2 = n; + item = string_list_append(push_item->string, show_info->list); + item->util = push_item->util; + return 0; +} + +/* + * Sorting comparison for a string list that has push_info + * structs in its util field + */ +static int cmp_string_with_push(const void *va, const void *vb) +{ + const struct string_list_item *a = va; + const struct string_list_item *b = vb; + const struct push_info *a_push = a->util; + const struct push_info *b_push = b->util; + int cmp = strcmp(a->string, b->string); + return cmp ? cmp : strcmp(a_push->dest, b_push->dest); +} + +int show_push_info_item(struct string_list_item *item, void *cb_data) +{ + struct show_info *show_info = cb_data; + struct push_info *push_info = item->util; + char *src = item->string, *status = NULL; + + switch (push_info->status) { + case PUSH_STATUS_CREATE: + status = "create"; + break; + case PUSH_STATUS_DELETE: + status = "delete"; + src = "(none)"; + break; + case PUSH_STATUS_UPTODATE: + status = "up to date"; + break; + case PUSH_STATUS_FASTFORWARD: + status = "fast forwardable"; + break; + case PUSH_STATUS_OUTOFDATE: + status = "local out of date"; + break; + case PUSH_STATUS_NOTQUERIED: + break; + } + if (status) + printf(" %-*s %s to %-*s (%s)\n", show_info->width, src, + push_info->forced ? "forces" : "pushes", + show_info->width2, push_info->dest, status); + else + printf(" %-*s %s to %s\n", show_info->width, src, + push_info->forced ? "forces" : "pushes", + push_info->dest); + return 0; +} + static int show(int argc, const char **argv) { - int no_query = 0, result = 0; + int no_query = 0, result = 0, query_flag = 0; struct option options[] = { OPT_GROUP("show specific options"), OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"), OPT_END() }; struct ref_states states; + struct string_list info_list = { NULL, 0, 0, 0 }; + struct show_info info; - argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + argc = parse_options(argc, argv, NULL, options, builtin_remote_usage, + 0); if (argc < 1) return show_all(); + if (!no_query) + query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES); + memset(&states, 0, sizeof(states)); + memset(&info, 0, sizeof(info)); + info.states = &states; + info.list = &info_list; for (; argc; argc--, argv++) { int i; - get_remote_ref_states(*argv, &states, !no_query); - - printf("* remote %s\n URL: %s\n", *argv, - states.remote->url_nr > 0 ? - states.remote->url[0] : "(no URL)"); - - for (i = 0; i < branch_list.nr; i++) { - struct string_list_item *branch = branch_list.items + i; - struct branch_info *info = branch->util; - int j; - - if (!info->merge.nr || strcmp(*argv, info->remote)) - continue; - printf(" Remote branch%s merged with 'git pull' " - "while on branch %s\n ", - info->merge.nr > 1 ? "es" : "", - branch->string); - for (j = 0; j < info->merge.nr; j++) - printf(" %s", info->merge.items[j].string); - printf("\n"); - } - - if (!no_query) { - show_list(" New remote branch%s (next fetch " - "will store in remotes/%s)", - &states.new, states.remote->name); - show_list(" Stale tracking branch%s (use 'git remote " - "prune')", &states.stale, ""); - } + get_remote_ref_states(*argv, &states, query_flag); + printf("* remote %s\n", *argv); + if (states.remote->url_nr) { + for (i=0; i < states.remote->url_nr; i++) + printf(" URL: %s\n", states.remote->url[i]); + } else + printf(" URL: %s\n", "(no URL)"); if (no_query) - for_each_ref(append_ref_to_tracked_list, &states); - show_list(" Tracked remote branch%s", &states.tracked, ""); - - if (states.remote->push_refspec_nr) { - printf(" Local branch%s pushed with 'git push'\n", - states.remote->push_refspec_nr > 1 ? - "es" : ""); - for (i = 0; i < states.remote->push_refspec_nr; i++) { - struct refspec *spec = states.remote->push + i; - printf(" %s%s%s%s\n", - spec->force ? "+" : "", - abbrev_branch(spec->src), - spec->dst ? ":" : "", - spec->dst ? abbrev_branch(spec->dst) : ""); - } + printf(" HEAD branch: (not queried)\n"); + else if (!states.heads.nr) + printf(" HEAD branch: (unknown)\n"); + else if (states.heads.nr == 1) + printf(" HEAD branch: %s\n", states.heads.items[0].string); + else { + printf(" HEAD branch (remote HEAD is ambiguous," + " may be one of the following):\n"); + for (i = 0; i < states.heads.nr; i++) + printf(" %s\n", states.heads.items[i].string); } - /* NEEDSWORK: free remote */ - string_list_clear(&states.new, 0); - string_list_clear(&states.stale, 0); - string_list_clear(&states.tracked, 0); + /* remote branch info */ + info.width = 0; + for_each_string_list(add_remote_to_show_info, &states.new, &info); + for_each_string_list(add_remote_to_show_info, &states.tracked, &info); + for_each_string_list(add_remote_to_show_info, &states.stale, &info); + if (info.list->nr) + printf(" Remote branch%s:%s\n", + info.list->nr > 1 ? "es" : "", + no_query ? " (status not queried)" : ""); + for_each_string_list(show_remote_info_item, info.list, &info); + string_list_clear(info.list, 0); + + /* git pull info */ + info.width = 0; + info.any_rebase = 0; + for_each_string_list(add_local_to_show_info, &branch_list, &info); + if (info.list->nr) + printf(" Local branch%s configured for 'git pull':\n", + info.list->nr > 1 ? "es" : ""); + for_each_string_list(show_local_info_item, info.list, &info); + string_list_clear(info.list, 0); + + /* git push info */ + if (states.remote->mirror) + printf(" Local refs will be mirrored by 'git push'\n"); + + info.width = info.width2 = 0; + for_each_string_list(add_push_to_show_info, &states.push, &info); + qsort(info.list->items, info.list->nr, + sizeof(*info.list->items), cmp_string_with_push); + if (info.list->nr) + printf(" Local ref%s configured for 'git push'%s:\n", + info.list->nr > 1 ? "s" : "", + no_query ? " (status not queried)" : ""); + for_each_string_list(show_push_info_item, info.list, &info); + string_list_clear(info.list, 0); + + free_remote_ref_states(&states); + } + + return result; +} + +static int set_head(int argc, const char **argv) +{ + int i, opt_a = 0, opt_d = 0, result = 0; + struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; + char *head_name = NULL; + + struct option options[] = { + OPT_GROUP("set-head specific options"), + OPT_BOOLEAN('a', "auto", &opt_a, + "set refs/remotes/<name>/HEAD according to remote"), + OPT_BOOLEAN('d', "delete", &opt_d, + "delete refs/remotes/<name>/HEAD"), + OPT_END() + }; + argc = parse_options(argc, argv, NULL, options, builtin_remote_usage, + 0); + if (argc) + strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]); + + if (!opt_a && !opt_d && argc == 2) { + head_name = xstrdup(argv[1]); + } else if (opt_a && !opt_d && argc == 1) { + struct ref_states states; + memset(&states, 0, sizeof(states)); + get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES); + if (!states.heads.nr) + result |= error("Cannot determine remote HEAD"); + else if (states.heads.nr > 1) { + result |= error("Multiple remote HEAD branches. " + "Please choose one explicitly with:"); + for (i = 0; i < states.heads.nr; i++) + fprintf(stderr, " git remote set-head %s %s\n", + argv[0], states.heads.items[i].string); + } else + head_name = xstrdup(states.heads.items[0].string); + free_remote_ref_states(&states); + } else if (opt_d && !opt_a && argc == 1) { + if (delete_ref(buf.buf, NULL, REF_NODEREF)) + result |= error("Could not delete %s", buf.buf); + } else + usage_with_options(builtin_remote_usage, options); + + if (head_name) { + unsigned char sha1[20]; + strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name); + /* make sure it's valid */ + if (!resolve_ref(buf2.buf, sha1, 1, NULL)) + result |= error("Not a valid ref: %s", buf2.buf); + else if (create_symref(buf.buf, buf2.buf, "remote set-head")) + result |= error("Could not setup %s", buf.buf); + if (opt_a) + printf("%s/HEAD set to %s\n", argv[0], head_name); + free(head_name); } + strbuf_release(&buf); + strbuf_release(&buf2); return result; } @@ -755,49 +1132,50 @@ static int prune(int argc, const char **argv) OPT__DRY_RUN(&dry_run), OPT_END() }; - struct ref_states states; - const char *dangling_msg; - argc = parse_options(argc, argv, options, builtin_remote_usage, 0); + argc = parse_options(argc, argv, NULL, 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; + for (; argc; argc--, argv++) + result |= prune_remote(*argv, dry_run); - get_remote_ref_states(*argv, &states, 1); + return result; +} - if (states.stale.nr) { - printf("Pruning %s\n", *argv); - printf("URL: %s\n", - states.remote->url_nr - ? states.remote->url[0] - : "(no URL)"); - } +static int prune_remote(const char *remote, int dry_run) +{ + int result = 0, i; + struct ref_states states; + const char *dangling_msg = dry_run + ? " %s will become dangling!\n" + : " %s has become dangling!\n"; - for (i = 0; i < states.stale.nr; i++) { - const char *refname = states.stale.items[i].util; + memset(&states, 0, sizeof(states)); + get_remote_ref_states(remote, &states, GET_REF_STATES); + + if (states.stale.nr) { + printf("Pruning %s\n", remote); + printf("URL: %s\n", + states.remote->url_nr + ? states.remote->url[0] + : "(no URL)"); + } - if (!dry_run) - result |= delete_ref(refname, NULL, 0); + for (i = 0; i < states.stale.nr; i++) { + const char *refname = states.stale.items[i].util; - printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", - abbrev_ref(refname, "refs/remotes/")); - warn_dangling_symref(dangling_msg, refname); - } + if (!dry_run) + result |= delete_ref(refname, NULL, 0); - /* NEEDSWORK: free remote */ - string_list_clear(&states.new, 0); - string_list_clear(&states.stale, 0); - string_list_clear(&states.tracked, 0); + printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned", + abbrev_ref(refname, "refs/remotes/")); + warn_dangling_symref(dangling_msg, refname); } + free_remote_ref_states(&states); return result; } @@ -814,16 +1192,18 @@ struct remote_group { struct string_list *list; } remote_group; -static int get_remote_group(const char *key, const char *value, void *cb) +static int get_remote_group(const char *key, const char *value, void *num_hits) { if (!prefixcmp(key, "remotes.") && !strcmp(key + 8, remote_group.name)) { /* split list by white space */ int space = strcspn(value, " \t\n"); while (*value) { - if (space > 1) + if (space > 1) { string_list_append(xstrndup(value, space), remote_group.list); + ++*((int *)num_hits); + } value += space + (value[space] != '\0'); space = strcspn(value, " \t\n"); } @@ -834,10 +1214,18 @@ static int get_remote_group(const char *key, const char *value, void *cb) static int update(int argc, const char **argv) { - int i, result = 0; + int i, result = 0, prune = 0; struct string_list list = { NULL, 0, 0, 0 }; static const char *default_argv[] = { NULL, "default", NULL }; + struct option options[] = { + OPT_GROUP("update specific options"), + OPT_BOOLEAN('p', "prune", &prune, + "prune remotes after fetching"), + OPT_END() + }; + argc = parse_options(argc, argv, NULL, options, builtin_remote_usage, + PARSE_OPT_KEEP_ARGV0); if (argc < 2) { argc = 2; argv = default_argv; @@ -845,15 +1233,28 @@ static int update(int argc, const char **argv) remote_group.list = &list; for (i = 1; i < argc; i++) { + int groups_found = 0; remote_group.name = argv[i]; - result = git_config(get_remote_group, NULL); + result = git_config(get_remote_group, &groups_found); + if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) { + struct remote *remote; + if (!remote_is_configured(argv[i])) + die("No such remote or remote group: %s", + argv[i]); + remote = remote_get(argv[i]); + string_list_append(remote->name, remote_group.list); + } } if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default")) result = for_each_remote(get_one_remote_for_update, &list); - for (i = 0; i < list.nr; i++) - result |= fetch_remote(list.items[i].string); + for (i = 0; i < list.nr; i++) { + int err = fetch_remote(list.items[i].string); + result |= err; + if (!err && prune) + result |= prune_remote(list.items[i].string, 0); + } /* all names were strdup()ed or strndup()ed */ list.strdup_strings = 1; @@ -909,7 +1310,7 @@ int cmd_remote(int argc, const char **argv, const char *prefix) }; int result; - argc = parse_options(argc, argv, options, builtin_remote_usage, + argc = parse_options(argc, argv, prefix, options, builtin_remote_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (argc < 1) @@ -920,6 +1321,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix) result = mv(argc, argv); else if (!strcmp(argv[0], "rm")) result = rm(argc, argv); + else if (!strcmp(argv[0], "set-head")) + result = set_head(argc, argv); else if (!strcmp(argv[0], "show")) result = show(argc, argv); else if (!strcmp(argv[0], "prune")) diff --git a/builtin-rerere.c b/builtin-rerere.c index bd8fc77a7a..adfb7b5f48 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -13,28 +13,17 @@ static const char git_rerere_usage[] = static int cutoff_noresolve = 15; static int cutoff_resolve = 60; -static const char *rr_path(const char *name, const char *file) -{ - return git_path("rr-cache/%s/%s", name, file); -} - static time_t rerere_created_at(const char *name) { struct stat st; - return stat(rr_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; -} - -static int has_resolution(const char *name) -{ - struct stat st; - return !stat(rr_path(name, "postimage"), &st); + return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime; } static void unlink_rr_item(const char *name) { - unlink(rr_path(name, "thisimage")); - unlink(rr_path(name, "preimage")); - unlink(rr_path(name, "postimage")); + unlink(rerere_path(name, "thisimage")); + unlink(rerere_path(name, "preimage")); + unlink(rerere_path(name, "postimage")); rmdir(git_path("rr-cache/%s", name)); } @@ -65,7 +54,7 @@ static void garbage_collect(struct string_list *rr) then = rerere_created_at(e->d_name); if (!then) continue; - cutoff = (has_resolution(e->d_name) + cutoff = (has_rerere_resolution(e->d_name) ? cutoff_resolve : cutoff_noresolve); if (then < now - cutoff * 86400) string_list_append(e->d_name, &to_remove); @@ -124,10 +113,10 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "clear")) { for (i = 0; i < merge_rr.nr; i++) { const char *name = (const char *)merge_rr.items[i].util; - if (!has_resolution(name)) + if (!has_rerere_resolution(name)) unlink_rr_item(name); } - unlink(git_path("rr-cache/MERGE_RR")); + unlink_or_warn(git_path("rr-cache/MERGE_RR")); } else if (!strcmp(argv[1], "gc")) garbage_collect(&merge_rr); else if (!strcmp(argv[1], "status")) @@ -137,7 +126,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix) for (i = 0; i < merge_rr.nr; i++) { const char *path = merge_rr.items[i].string; const char *name = (const char *)merge_rr.items[i].util; - diff_two(rr_path(name, "preimage"), path, path, path); + diff_two(rerere_path(name, "preimage"), path, path, path); } else usage(git_rerere_usage); diff --git a/builtin-reset.c b/builtin-reset.c index c0cb915c26..5fa1789d0c 100644 --- a/builtin-reset.c +++ b/builtin-reset.c @@ -203,7 +203,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - argc = parse_options(argc, argv, options, git_reset_usage, + argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); reflog_action = args_to_str(argv); setenv("GIT_REFLOG_ACTION", reflog_action, 0); @@ -228,7 +228,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } /* * Otherwise, argv[i] could be either <rev> or <paths> and - * has to be unambigous. + * has to be unambiguous. */ else if (!get_sha1(argv[i], sha1)) { /* diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 0af7cd94f9..4ba1c12e0b 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -1,20 +1,12 @@ #include "cache.h" -#include "refs.h" -#include "tag.h" #include "commit.h" -#include "tree.h" -#include "blob.h" -#include "tree-walk.h" #include "diff.h" #include "revision.h" #include "list-objects.h" #include "builtin.h" #include "log-tree.h" #include "graph.h" - -/* bits #0-15 in revision.h */ - -#define COUNTED (1u<<16) +#include "bisect.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -50,73 +42,69 @@ static const char rev_list_usage[] = " --bisect-all" ; -static struct rev_info revs; - -static int bisect_list; -static int show_timestamp; -static int hdr_termination; -static const char *header_prefix; - -static void finish_commit(struct commit *commit); -static void show_commit(struct commit *commit) +static void finish_commit(struct commit *commit, void *data); +static void show_commit(struct commit *commit, void *data) { - graph_show_commit(revs.graph); + struct rev_list_info *info = data; + struct rev_info *revs = info->revs; + + graph_show_commit(revs->graph); - if (show_timestamp) + if (info->show_timestamp) printf("%lu ", commit->date); - if (header_prefix) - fputs(header_prefix, stdout); + if (info->header_prefix) + fputs(info->header_prefix, stdout); - if (!revs.graph) { + if (!revs->graph) { if (commit->object.flags & BOUNDARY) putchar('-'); else if (commit->object.flags & UNINTERESTING) putchar('^'); - else if (revs.left_right) { + else if (revs->left_right) { if (commit->object.flags & SYMMETRIC_LEFT) putchar('<'); else putchar('>'); } } - if (revs.abbrev_commit && revs.abbrev) - fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev), + if (revs->abbrev_commit && revs->abbrev) + fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev), stdout); else fputs(sha1_to_hex(commit->object.sha1), stdout); - if (revs.print_parents) { + if (revs->print_parents) { struct commit_list *parents = commit->parents; while (parents) { printf(" %s", sha1_to_hex(parents->item->object.sha1)); parents = parents->next; } } - if (revs.children.name) { + if (revs->children.name) { struct commit_list *children; - children = lookup_decoration(&revs.children, &commit->object); + children = lookup_decoration(&revs->children, &commit->object); while (children) { printf(" %s", sha1_to_hex(children->item->object.sha1)); children = children->next; } } - show_decorations(&revs, commit); - if (revs.commit_format == CMIT_FMT_ONELINE) + show_decorations(revs, commit); + if (revs->commit_format == CMIT_FMT_ONELINE) putchar(' '); else putchar('\n'); - if (revs.verbose_header && commit->buffer) { + if (revs->verbose_header && commit->buffer) { struct strbuf buf = STRBUF_INIT; - pretty_print_commit(revs.commit_format, commit, - &buf, revs.abbrev, NULL, NULL, - revs.date_mode, 0); - if (revs.graph) { + pretty_print_commit(revs->commit_format, commit, + &buf, revs->abbrev, NULL, NULL, + revs->date_mode, 0); + if (revs->graph) { if (buf.len) { - if (revs.commit_format != CMIT_FMT_ONELINE) - graph_show_oneline(revs.graph); + if (revs->commit_format != CMIT_FMT_ONELINE) + graph_show_oneline(revs->graph); - graph_show_commit_msg(revs.graph, &buf); + graph_show_commit_msg(revs->graph, &buf); /* * Add a newline after the commit message. @@ -134,7 +122,7 @@ static void show_commit(struct commit *commit) * format doesn't explicitly end in a newline.) */ if (buf.len && buf.buf[buf.len - 1] == '\n') - graph_show_padding(revs.graph); + graph_show_padding(revs->graph); putchar('\n'); } else { /* @@ -142,23 +130,23 @@ static void show_commit(struct commit *commit) * the rest of the graph output for this * commit. */ - if (graph_show_remainder(revs.graph)) + if (graph_show_remainder(revs->graph)) putchar('\n'); } } else { if (buf.len) - printf("%s%c", buf.buf, hdr_termination); + printf("%s%c", buf.buf, info->hdr_termination); } strbuf_release(&buf); } else { - if (graph_show_remainder(revs.graph)) + if (graph_show_remainder(revs->graph)) putchar('\n'); } maybe_flush_or_die(stdout, "stdout"); - finish_commit(commit); + finish_commit(commit, data); } -static void finish_commit(struct commit *commit) +static void finish_commit(struct commit *commit, void *data) { if (commit->parents) { free_commit_list(commit->parents); @@ -198,389 +186,127 @@ static void show_edge(struct commit *commit) printf("-%s\n", sha1_to_hex(commit->object.sha1)); } -/* - * This is a truly stupid algorithm, but it's only - * used for bisection, and we just don't care enough. - * - * We care just barely enough to avoid recursing for - * non-merge entries. - */ -static int count_distance(struct commit_list *entry) +static inline int log2i(int n) { - int nr = 0; - - while (entry) { - struct commit *commit = entry->item; - struct commit_list *p; - - if (commit->object.flags & (UNINTERESTING | COUNTED)) - break; - if (!(commit->object.flags & TREESAME)) - nr++; - commit->object.flags |= COUNTED; - p = commit->parents; - entry = p; - if (p) { - p = p->next; - while (p) { - nr += count_distance(p); - p = p->next; - } - } - } + int log2 = 0; - return nr; -} + for (; n > 1; n >>= 1) + log2++; -static void clear_distance(struct commit_list *list) -{ - while (list) { - struct commit *commit = list->item; - commit->object.flags &= ~COUNTED; - list = list->next; - } + return log2; } -#define DEBUG_BISECT 0 - -static inline int weight(struct commit_list *elem) +static inline int exp2i(int n) { - return *((int*)(elem->item->util)); + return 1 << n; } -static inline void weight_set(struct commit_list *elem, int weight) +/* + * Estimate the number of bisect steps left (after the current step) + * + * For any x between 0 included and 2^n excluded, the probability for + * n - 1 steps left looks like: + * + * P(2^n + x) == (2^n - x) / (2^n + x) + * + * and P(2^n + x) < 0.5 means 2^n < 3x + */ +int estimate_bisect_steps(int all) { - *((int*)(elem->item->util)) = weight; -} + int n, x, e; -static int count_interesting_parents(struct commit *commit) -{ - struct commit_list *p; - int count; + if (all < 3) + return 0; - for (count = 0, p = commit->parents; p; p = p->next) { - if (p->item->object.flags & UNINTERESTING) - continue; - count++; - } - return count; -} + n = log2i(all); + e = exp2i(n); + x = all - e; -static inline int halfway(struct commit_list *p, int nr) -{ - /* - * Don't short-cut something we are not going to return! - */ - if (p->item->object.flags & TREESAME) - return 0; - if (DEBUG_BISECT) - return 0; - /* - * 2 and 3 are halfway of 5. - * 3 is halfway of 6 but 2 and 4 are not. - */ - switch (2 * weight(p) - nr) { - case -1: case 0: case 1: - return 1; - default: - return 0; - } + return (e < 3 * x) ? n : n - 1; } -#if !DEBUG_BISECT -#define show_list(a,b,c,d) do { ; } while (0) -#else -static void show_list(const char *debug, int counted, int nr, - struct commit_list *list) +void print_commit_list(struct commit_list *list, + const char *format_cur, + const char *format_last) { - struct commit_list *p; - - fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr); - - for (p = list; p; p = p->next) { - struct commit_list *pp; - struct commit *commit = p->item; - unsigned flags = commit->object.flags; - enum object_type type; - unsigned long size; - char *buf = read_sha1_file(commit->object.sha1, &type, &size); - char *ep, *sp; - - fprintf(stderr, "%c%c%c ", - (flags & TREESAME) ? ' ' : 'T', - (flags & UNINTERESTING) ? 'U' : ' ', - (flags & COUNTED) ? 'C' : ' '); - if (commit->util) - fprintf(stderr, "%3d", weight(p)); - else - fprintf(stderr, "---"); - fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1)); - for (pp = commit->parents; pp; pp = pp->next) - fprintf(stderr, " %.*s", 8, - sha1_to_hex(pp->item->object.sha1)); - - sp = strstr(buf, "\n\n"); - if (sp) { - sp += 2; - for (ep = sp; *ep && *ep != '\n'; ep++) - ; - fprintf(stderr, " %.*s", (int)(ep - sp), sp); - } - fprintf(stderr, "\n"); + for ( ; list; list = list->next) { + const char *format = list->next ? format_cur : format_last; + printf(format, sha1_to_hex(list->item->object.sha1)); } } -#endif /* DEBUG_BISECT */ -static struct commit_list *best_bisection(struct commit_list *list, int nr) +static void show_tried_revs(struct commit_list *tried) { - struct commit_list *p, *best; - int best_distance = -1; - - best = list; - for (p = list; p; p = p->next) { - int distance; - unsigned flags = p->item->object.flags; - - if (flags & TREESAME) - continue; - distance = weight(p); - if (nr - distance < distance) - distance = nr - distance; - if (distance > best_distance) { - best = p; - best_distance = distance; - } - } - - return best; + printf("bisect_tried='"); + print_commit_list(tried, "%s|", "%s"); + printf("'\n"); } -struct commit_dist { - struct commit *commit; - int distance; -}; - -static int compare_commit_dist(const void *a_, const void *b_) +static void print_var_str(const char *var, const char *val) { - struct commit_dist *a, *b; - - a = (struct commit_dist *)a_; - b = (struct commit_dist *)b_; - if (a->distance != b->distance) - return b->distance - a->distance; /* desc sort */ - return hashcmp(a->commit->object.sha1, b->commit->object.sha1); + printf("%s='%s'\n", var, val); } -static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr) +static void print_var_int(const char *var, int val) { - struct commit_list *p; - struct commit_dist *array = xcalloc(nr, sizeof(*array)); - int cnt, i; - - for (p = list, cnt = 0; p; p = p->next) { - int distance; - unsigned flags = p->item->object.flags; - - if (flags & TREESAME) - continue; - distance = weight(p); - if (nr - distance < distance) - distance = nr - distance; - array[cnt].commit = p->item; - array[cnt].distance = distance; - cnt++; - } - qsort(array, cnt, sizeof(*array), compare_commit_dist); - for (p = list, i = 0; i < cnt; i++) { - struct name_decoration *r = xmalloc(sizeof(*r) + 100); - struct object *obj = &(array[i].commit->object); - - sprintf(r->name, "dist=%d", array[i].distance); - r->next = add_decoration(&name_decoration, obj, r); - p->item = array[i].commit; - p = p->next; - } - if (p) - p->next = NULL; - free(array); - return list; + printf("%s=%d\n", var, val); } -/* - * zero or positive weight is the number of interesting commits it can - * reach, including itself. Especially, weight = 0 means it does not - * reach any tree-changing commits (e.g. just above uninteresting one - * but traversal is with pathspec). - * - * weight = -1 means it has one parent and its distance is yet to - * be computed. - * - * weight = -2 means it has more than one parent and its distance is - * unknown. After running count_distance() first, they will get zero - * or positive distance. - */ -static struct commit_list *do_find_bisection(struct commit_list *list, - int nr, int *weights, - int find_all) +int show_bisect_vars(struct rev_list_info *info, int reaches, int all) { - int n, counted; - struct commit_list *p; - - counted = 0; - - for (n = 0, p = list; p; p = p->next) { - struct commit *commit = p->item; - unsigned flags = commit->object.flags; - - p->item->util = &weights[n++]; - switch (count_interesting_parents(commit)) { - case 0: - if (!(flags & TREESAME)) { - weight_set(p, 1); - counted++; - show_list("bisection 2 count one", - counted, nr, list); - } - /* - * otherwise, it is known not to reach any - * tree-changing commit and gets weight 0. - */ - break; - case 1: - weight_set(p, -1); - break; - default: - weight_set(p, -2); - break; - } - } + int cnt, flags = info->bisect_show_flags; + char hex[41] = ""; + struct commit_list *tried; + struct rev_info *revs = info->revs; + + if (!revs->commits && !(flags & BISECT_SHOW_TRIED)) + return 1; - show_list("bisection 2 initialize", counted, nr, list); + revs->commits = filter_skipped(revs->commits, &tried, + flags & BISECT_SHOW_ALL, + NULL, NULL); /* - * If you have only one parent in the resulting set - * then you can reach one commit more than that parent - * can reach. So we do not have to run the expensive - * count_distance() for single strand of pearls. - * - * However, if you have more than one parents, you cannot - * just add their distance and one for yourself, since - * they usually reach the same ancestor and you would - * end up counting them twice that way. - * - * So we will first count distance of merges the usual - * way, and then fill the blanks using cheaper algorithm. + * revs->commits can reach "reaches" commits among + * "all" commits. If it is good, then there are + * (all-reaches) commits left to be bisected. + * On the other hand, if it is bad, then the set + * to bisect is "reaches". + * A bisect set of size N has (N-1) commits further + * to test, as we already know one bad one. */ - for (p = list; p; p = p->next) { - if (p->item->object.flags & UNINTERESTING) - continue; - if (weight(p) != -2) - continue; - weight_set(p, count_distance(p)); - clear_distance(list); - - /* Does it happen to be at exactly half-way? */ - if (!find_all && halfway(p, nr)) - return p; - counted++; - } - - show_list("bisection 2 count_distance", counted, nr, list); + cnt = all - reaches; + if (cnt < reaches) + cnt = reaches; - while (counted < nr) { - for (p = list; p; p = p->next) { - struct commit_list *q; - unsigned flags = p->item->object.flags; + if (revs->commits) + strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1)); - if (0 <= weight(p)) - continue; - for (q = p->item->parents; q; q = q->next) { - if (q->item->object.flags & UNINTERESTING) - continue; - if (0 <= weight(q)) - break; - } - if (!q) - continue; - - /* - * weight for p is unknown but q is known. - * add one for p itself if p is to be counted, - * otherwise inherit it from q directly. - */ - if (!(flags & TREESAME)) { - weight_set(p, weight(q)+1); - counted++; - show_list("bisection 2 count one", - counted, nr, list); - } - else - weight_set(p, weight(q)); - - /* Does it happen to be at exactly half-way? */ - if (!find_all && halfway(p, nr)) - return p; - } + if (flags & BISECT_SHOW_ALL) { + traverse_commit_list(revs, show_commit, show_object, info); + printf("------\n"); } - show_list("bisection 2 counted all", counted, nr, list); + if (flags & BISECT_SHOW_TRIED) + show_tried_revs(tried); - if (!find_all) - return best_bisection(list, nr); - else - return best_bisection_sorted(list, nr); -} - -static struct commit_list *find_bisection(struct commit_list *list, - int *reaches, int *all, - int find_all) -{ - int nr, on_list; - struct commit_list *p, *best, *next, *last; - int *weights; + print_var_str("bisect_rev", hex); + print_var_int("bisect_nr", cnt - 1); + print_var_int("bisect_good", all - reaches - 1); + print_var_int("bisect_bad", reaches - 1); + print_var_int("bisect_all", all); + print_var_int("bisect_steps", estimate_bisect_steps(all)); - show_list("bisection 2 entry", 0, 0, list); - - /* - * Count the number of total and tree-changing items on the - * list, while reversing the list. - */ - for (nr = on_list = 0, last = NULL, p = list; - p; - p = next) { - unsigned flags = p->item->object.flags; - - next = p->next; - if (flags & UNINTERESTING) - continue; - p->next = last; - last = p; - if (!(flags & TREESAME)) - nr++; - on_list++; - } - list = last; - show_list("bisection 2 sorted", 0, nr, list); - - *all = nr; - weights = xcalloc(on_list, sizeof(*weights)); - - /* Do the real work of finding bisection commit. */ - best = do_find_bisection(list, nr, weights, find_all); - if (best) { - if (!find_all) - best->next = NULL; - *reaches = weight(best); - } - free(weights); - return best; + return 0; } int cmd_rev_list(int argc, const char **argv, const char *prefix) { - struct commit_list *list; + struct rev_info revs; + struct rev_list_info info; int i; int read_from_stdin = 0; + int bisect_list = 0; int bisect_show_vars = 0; int bisect_find_all = 0; int quiet = 0; @@ -591,6 +317,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.commit_format = CMIT_FMT_UNSPECIFIED; argc = setup_revisions(argc, argv, &revs, NULL); + memset(&info, 0, sizeof(info)); + info.revs = &revs; + quiet = DIFF_OPT_TST(&revs.diffopt, QUIET); for (i = 1 ; i < argc; i++) { const char *arg = argv[i]; @@ -600,7 +329,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--timestamp")) { - show_timestamp = 1; + info.show_timestamp = 1; continue; } if (!strcmp(arg, "--bisect")) { @@ -610,6 +339,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--bisect-all")) { bisect_list = 1; bisect_find_all = 1; + info.bisect_show_flags = BISECT_SHOW_ALL; revs.show_decorations = 1; continue; } @@ -629,19 +359,17 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } if (revs.commit_format != CMIT_FMT_UNSPECIFIED) { /* The command line has a --pretty */ - hdr_termination = '\n'; + info.hdr_termination = '\n'; if (revs.commit_format == CMIT_FMT_ONELINE) - header_prefix = ""; + info.header_prefix = ""; else - header_prefix = "commit "; + info.header_prefix = "commit "; } else if (revs.verbose_header) /* Only --header was specified */ revs.commit_format = CMIT_FMT_RAW; - list = revs.commits; - - if ((!list && + if ((!revs.commits && (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending.nr)) || revs.diff) @@ -662,47 +390,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) revs.commits = find_bisection(revs.commits, &reaches, &all, bisect_find_all); - if (bisect_show_vars) { - int cnt; - char hex[41]; - if (!revs.commits) - return 1; - /* - * revs.commits can reach "reaches" commits among - * "all" commits. If it is good, then there are - * (all-reaches) commits left to be bisected. - * On the other hand, if it is bad, then the set - * to bisect is "reaches". - * A bisect set of size N has (N-1) commits further - * to test, as we already know one bad one. - */ - cnt = all - reaches; - if (cnt < reaches) - cnt = reaches; - strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1)); - - if (bisect_find_all) { - traverse_commit_list(&revs, show_commit, show_object); - printf("------\n"); - } - printf("bisect_rev=%s\n" - "bisect_nr=%d\n" - "bisect_good=%d\n" - "bisect_bad=%d\n" - "bisect_all=%d\n", - hex, - cnt - 1, - all - reaches - 1, - reaches - 1, - all); - return 0; - } + if (bisect_show_vars) + return show_bisect_vars(&info, reaches, all); } traverse_commit_list(&revs, - quiet ? finish_commit : show_commit, - quiet ? finish_object : show_object); + quiet ? finish_commit : show_commit, + quiet ? finish_object : show_object, + &info); return 0; } diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index 81d5a6ffc9..112d622cda 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -26,6 +26,8 @@ static int show_type = NORMAL; #define SHOW_SYMBOLIC_FULL 2 static int symbolic; static int abbrev; +static int abbrev_ref; +static int abbrev_ref_strict; static int output_sq; /* @@ -109,8 +111,8 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) return; def = NULL; - if (symbolic && name) { - if (symbolic == SHOW_SYMBOLIC_FULL) { + if ((symbolic || abbrev_ref) && name) { + if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) { unsigned char discard[20]; char *full; @@ -125,6 +127,9 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) */ break; case 1: /* happy */ + if (abbrev_ref) + full = shorten_unambiguous_ref(full, + abbrev_ref_strict); show_with_type(type, full); break; default: /* ambiguous */ @@ -313,7 +318,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) int onb = 0, osz = 0, unb = 0, usz = 0; strbuf_addstr(&parsed, "set --"); - argc = parse_options(argc, argv, parseopt_opts, parseopt_usage, + argc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage, PARSE_OPT_KEEP_DASHDASH); if (argc < 1 || strcmp(argv[0], "--")) usage_with_options(parseopt_usage, parseopt_opts); @@ -388,7 +393,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) /* put an OPT_END() */ ALLOC_GROW(opts, onb + 1, osz); memset(opts + onb, 0, sizeof(opts[onb])); - argc = parse_options(argc, argv, opts, usage, + argc = parse_options(argc, argv, prefix, opts, usage, keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0); strbuf_addf(&parsed, " --"); @@ -397,6 +402,18 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) return 0; } +static int cmd_sq_quote(int argc, const char **argv) +{ + struct strbuf buf = STRBUF_INIT; + + if (argc) + sq_quote_argv(&buf, argv, 0); + printf("%s\n", buf.buf); + strbuf_release(&buf); + + return 0; +} + static void die_no_single_rev(int quiet) { if (quiet) @@ -414,6 +431,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); + if (argc > 1 && !strcmp("--sq-quote", argv[1])) + return cmd_sq_quote(argc - 2, argv + 2); + prefix = setup_git_directory(); git_config(git_default_config, NULL); for (i = 1; i < argc; i++) { @@ -506,6 +526,20 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) symbolic = SHOW_SYMBOLIC_FULL; continue; } + if (!prefixcmp(arg, "--abbrev-ref") && + (!arg[12] || arg[12] == '=')) { + abbrev_ref = 1; + abbrev_ref_strict = warn_ambiguous_refs; + if (arg[12] == '=') { + if (!strcmp(arg + 13, "strict")) + abbrev_ref_strict = 1; + else if (!strcmp(arg + 13, "loose")) + abbrev_ref_strict = 0; + else + die("unknown mode for %s", arg); + } + continue; + } if (!strcmp(arg, "--all")) { for_each_ref(show_reference, NULL); continue; diff --git a/builtin-revert.c b/builtin-revert.c index 3f2614e1bb..c87115af30 100644 --- a/builtin-revert.c +++ b/builtin-revert.c @@ -60,7 +60,7 @@ static void parse_args(int argc, const char **argv) OPT_END(), }; - if (parse_options(argc, argv, options, usage_str, 0) != 1) + if (parse_options(argc, argv, NULL, options, usage_str, 0) != 1) usage_with_options(usage_str, options); arg = argv[0]; @@ -323,9 +323,9 @@ static int revert_or_cherry_pick(int argc, const char **argv) encoding = get_encoding(message); if (!encoding) - encoding = "utf-8"; + encoding = "UTF-8"; if (!git_commit_encoding) - git_commit_encoding = "utf-8"; + git_commit_encoding = "UTF-8"; if ((reencoded_message = reencode_string(message, git_commit_encoding, encoding))) message = reencoded_message; diff --git a/builtin-rm.c b/builtin-rm.c index c11f455858..0cc4912718 100644 --- a/builtin-rm.c +++ b/builtin-rm.c @@ -59,8 +59,7 @@ static int check_local_mod(unsigned char *head, int index_only) if (lstat(ce->name, &st) < 0) { if (errno != ENOENT) - fprintf(stderr, "warning: '%s': %s", - ce->name, strerror(errno)); + warning("'%s': %s", ce->name, strerror(errno)); /* It already vanished from the working tree */ continue; } @@ -158,7 +157,8 @@ int cmd_rm(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); - argc = parse_options(argc, argv, builtin_rm_options, builtin_rm_usage, 0); + argc = parse_options(argc, argv, prefix, builtin_rm_options, + builtin_rm_usage, 0); if (!argc) usage_with_options(builtin_rm_usage, builtin_rm_options); diff --git a/builtin-send-pack.c b/builtin-send-pack.c index d65d019692..c375a3dbde 100644 --- a/builtin-send-pack.c +++ b/builtin-send-pack.c @@ -1,6 +1,5 @@ #include "cache.h" #include "commit.h" -#include "tag.h" #include "refs.h" #include "pkt-line.h" #include "run-command.h" @@ -11,9 +10,7 @@ static const char send_pack_usage[] = "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n" " --all and explicit <ref> specification are mutually exclusive."; -static struct send_pack_args args = { - /* .receivepack = */ "git-receive-pack", -}; +static struct send_pack_args args; static int feed_object(const unsigned char *sha1, int fd, int negative) { @@ -32,7 +29,7 @@ static int feed_object(const unsigned char *sha1, int fd, int negative) /* * Make a pack stream and spit it out into file descriptor fd */ -static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra) +static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args) { /* * The child becomes pack-objects --revs; we feed @@ -46,12 +43,16 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext "--stdout", NULL, NULL, + NULL, }; struct child_process po; int i; - if (args.use_thin_pack) - argv[4] = "--thin"; + i = 4; + if (args->use_thin_pack) + argv[i++] = "--thin"; + if (args->use_ofs_delta) + argv[i++] = "--delta-base-offset"; memset(&po, 0, sizeof(po)); po.argv = argv; po.in = -1; @@ -84,82 +85,6 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext return 0; } -static void unmark_and_free(struct commit_list *list, unsigned int mark) -{ - while (list) { - struct commit_list *temp = list; - temp->item->object.flags &= ~mark; - list = temp->next; - free(temp); - } -} - -static int ref_newer(const unsigned char *new_sha1, - const unsigned char *old_sha1) -{ - struct object *o; - struct commit *old, *new; - struct commit_list *list, *used; - int found = 0; - - /* Both new and old must be commit-ish and new is descendant of - * old. Otherwise we require --force. - */ - o = deref_tag(parse_object(old_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - old = (struct commit *) o; - - o = deref_tag(parse_object(new_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - new = (struct commit *) o; - - if (parse_commit(new) < 0) - return 0; - - used = list = NULL; - commit_list_insert(new, &list); - while (list) { - new = pop_most_recent_commit(&list, 1); - commit_list_insert(new, &used); - if (new == old) { - found = 1; - break; - } - } - unmark_and_free(list, 1); - unmark_and_free(used, 1); - return found; -} - -static struct ref *local_refs, **local_tail; -static struct ref *remote_refs, **remote_tail; - -static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct ref *ref; - int len; - - /* we already know it starts with refs/ to get here */ - if (check_ref_format(refname + 5)) - return 0; - - len = strlen(refname) + 1; - ref = xcalloc(1, sizeof(*ref) + len); - hashcpy(ref->new_sha1, sha1); - memcpy(ref->name, refname, len); - *local_tail = ref; - local_tail = &ref->next; - return 0; -} - -static void get_local_heads(void) -{ - local_tail = &local_refs; - for_each_ref(one_local_ref, NULL); -} - static int receive_status(int in, struct ref *refs) { struct ref *hint; @@ -247,25 +172,15 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref) } } -static const char *prettify_ref(const struct ref *ref) -{ - const char *name = ref->name; - return name + ( - !prefixcmp(name, "refs/heads/") ? 11 : - !prefixcmp(name, "refs/tags/") ? 10 : - !prefixcmp(name, "refs/remotes/") ? 13 : - 0); -} - #define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg) { fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); if (from) - fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to)); + fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); else - fputs(prettify_ref(to), stderr); + fputs(prettify_refname(to->name), stderr); if (msg) { fputs(" (", stderr); fputs(msg, stderr); @@ -385,47 +300,31 @@ static int refs_pushed(struct ref *ref) return 0; } -static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec) +int send_pack(struct send_pack_args *args, + int fd[], struct child_process *conn, + struct ref *remote_refs, + struct extra_have_objects *extra_have) { + int in = fd[0]; + int out = fd[1]; struct ref *ref; int new_refs; int ask_for_status_report = 0; int allow_deleting_refs = 0; int expect_status_report = 0; - int flags = MATCH_REFS_NONE; int ret; - struct extra_have_objects extra_have; - - memset(&extra_have, 0, sizeof(extra_have)); - if (args.send_all) - flags |= MATCH_REFS_ALL; - if (args.send_mirror) - flags |= MATCH_REFS_MIRROR; - - /* No funny business with the matcher */ - remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL, - &extra_have); - get_local_heads(); /* Does the other end support the reporting? */ if (server_supports("report-status")) ask_for_status_report = 1; if (server_supports("delete-refs")) allow_deleting_refs = 1; - - /* match them up */ - if (!remote_tail) - remote_tail = &remote_refs; - if (match_refs(local_refs, remote_refs, &remote_tail, - nr_refspec, refspec, flags)) { - close(out); - return -1; - } + if (server_supports("ofs-delta")) + args->use_ofs_delta = 1; if (!remote_refs) { fprintf(stderr, "No refs in common and none specified; doing nothing.\n" "Perhaps you should specify a branch such as 'master'.\n"); - close(out); return 0; } @@ -437,7 +336,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest if (ref->peer_ref) hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - else if (!args.send_mirror) + else if (!args->send_mirror) continue; ref->deletion = is_null_sha1(ref->new_sha1); @@ -476,7 +375,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest (!has_sha1_file(ref->old_sha1) || !ref_newer(ref->new_sha1, ref->old_sha1)); - if (ref->nonfastforward && !ref->force && !args.force_update) { + if (ref->nonfastforward && !ref->force && !args->force_update) { ref->status = REF_STATUS_REJECT_NONFASTFORWARD; continue; } @@ -484,7 +383,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest if (!ref->deletion) new_refs++; - if (!args.dry_run) { + if (!args->dry_run) { char *old_hex = sha1_to_hex(ref->old_sha1); char *new_hex = sha1_to_hex(ref->new_sha1); @@ -505,27 +404,19 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest } packet_flush(out); - if (new_refs && !args.dry_run) { - if (pack_objects(out, remote_refs, &extra_have) < 0) + if (new_refs && !args->dry_run) { + if (pack_objects(out, remote_refs, extra_have, args) < 0) { + for (ref = remote_refs; ref; ref = ref->next) + ref->status = REF_STATUS_NONE; return -1; + } } - else - close(out); if (expect_status_report) ret = receive_status(in, remote_refs); else ret = 0; - print_push_status(dest, remote_refs); - - if (!args.dry_run && remote) { - for (ref = remote_refs; ref; ref = ref->next) - update_tracking_ref(remote, ref); - } - - if (!refs_pushed(remote_refs)) - fprintf(stderr, "Everything up-to-date\n"); if (ret < 0) return ret; for (ref = remote_refs; ref; ref = ref->next) { @@ -574,11 +465,19 @@ static void verify_remote_names(int nr_heads, const char **heads) int cmd_send_pack(int argc, const char **argv, const char *prefix) { - int i, nr_heads = 0; - const char **heads = NULL; + int i, nr_refspecs = 0; + const char **refspecs = NULL; const char *remote_name = NULL; struct remote *remote = NULL; const char *dest = NULL; + int fd[2]; + struct child_process *conn; + struct extra_have_objects extra_have; + struct ref *remote_refs, *local_refs; + int ret; + int send_all = 0; + const char *receivepack = "git-receive-pack"; + int flags; argv++; for (i = 1; i < argc; i++, argv++) { @@ -586,11 +485,11 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (*arg == '-') { if (!prefixcmp(arg, "--receive-pack=")) { - args.receivepack = arg + 15; + receivepack = arg + 15; continue; } if (!prefixcmp(arg, "--exec=")) { - args.receivepack = arg + 7; + receivepack = arg + 7; continue; } if (!prefixcmp(arg, "--remote=")) { @@ -598,7 +497,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--all")) { - args.send_all = 1; + send_all = 1; continue; } if (!strcmp(arg, "--dry-run")) { @@ -627,8 +526,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) dest = arg; continue; } - heads = (const char **) argv; - nr_heads = argc - i; + refspecs = (const char **) argv; + nr_refspecs = argc - i; break; } if (!dest) @@ -637,8 +536,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) * --all and --mirror are incompatible; neither makes sense * with any refspecs. */ - if ((heads && (args.send_all || args.send_mirror)) || - (args.send_all && args.send_mirror)) + if ((refspecs && (send_all || args.send_mirror)) || + (send_all && args.send_mirror)) usage(send_pack_usage); if (remote_name) { @@ -649,24 +548,45 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) } } - return send_pack(&args, dest, remote, nr_heads, heads); -} + conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0); -int send_pack(struct send_pack_args *my_args, - const char *dest, struct remote *remote, - int nr_heads, const char **heads) -{ - int fd[2], ret; - struct child_process *conn; + memset(&extra_have, 0, sizeof(extra_have)); + + get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL, + &extra_have); - memcpy(&args, my_args, sizeof(args)); + verify_remote_names(nr_refspecs, refspecs); - verify_remote_names(nr_heads, heads); + local_refs = get_local_heads(); - conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0); - ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads); + flags = MATCH_REFS_NONE; + + if (send_all) + flags |= MATCH_REFS_ALL; + if (args.send_mirror) + flags |= MATCH_REFS_MIRROR; + + /* match them up */ + if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags)) + return -1; + + ret = send_pack(&args, fd, conn, remote_refs, &extra_have); + + close(fd[1]); close(fd[0]); - /* do_send_pack always closes fd[1] */ + ret |= finish_connect(conn); - return !!ret; + + print_push_status(dest, remote_refs); + + if (!args.dry_run && remote) { + struct ref *ref; + for (ref = remote_refs; ref; ref = ref->next) + update_tracking_ref(remote, ref); + } + + if (!ret && !refs_pushed(remote_refs)) + fprintf(stderr, "Everything up-to-date\n"); + + return ret; } diff --git a/builtin-shortlog.c b/builtin-shortlog.c index badd912038..6a3812ee18 100644 --- a/builtin-shortlog.c +++ b/builtin-shortlog.c @@ -101,7 +101,6 @@ static void insert_one_record(struct shortlog *log, } while (*oneline && isspace(*oneline) && *oneline != '\n') oneline++; - len = eol - oneline; format_subject(&subject, oneline, " "); buffer = strbuf_detach(&subject, NULL); @@ -264,7 +263,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); shortlog_init(&log); init_revisions(&rev, prefix); - parse_options_start(&ctx, argc, argv, PARSE_OPT_KEEP_DASHDASH | + parse_options_start(&ctx, argc, argv, prefix, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 306b850c72..01bea3b583 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -2,11 +2,26 @@ #include "commit.h" #include "refs.h" #include "builtin.h" +#include "color.h" +#include "parse-options.h" -static const char show_branch_usage[] = -"git show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n[,b]] <branch>"; -static const char show_branch_usage_reflog[] = -"--reflog is incompatible with --all, --remotes, --independent or --merge-base"; +static const char* show_branch_usage[] = { + "git show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base] [--topics] [--color] [<refs>...]", + "--reflog[=n[,b]] [--list] [--color] <branch>", + NULL +}; + +static int showbranch_use_color = -1; +static char column_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RED, + GIT_COLOR_GREEN, + GIT_COLOR_YELLOW, + GIT_COLOR_BLUE, + GIT_COLOR_MAGENTA, + GIT_COLOR_CYAN, +}; + +#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) static int default_num; static int default_alloc; @@ -19,6 +34,20 @@ static const char **default_arg; #define DEFAULT_REFLOG 4 +static const char *get_color_code(int idx) +{ + if (showbranch_use_color) + return column_colors[idx]; + return ""; +} + +static const char *get_color_reset_code(void) +{ + if (showbranch_use_color) + return GIT_COLOR_RESET; + return ""; +} + static struct commit *interesting(struct commit_list *list) { while (list) { @@ -365,8 +394,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, return 0; } if (MAX_REVS <= ref_name_cnt) { - fprintf(stderr, "warning: ignoring %s; " - "cannot handle more than %d refs\n", + warning("ignoring %s; cannot handle more than %d refs", refname, MAX_REVS); return 0; } @@ -546,7 +574,12 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) return 0; } - return git_default_config(var, value, cb); + if (!strcmp(var, "color.showbranch")) { + showbranch_use_color = git_config_colorbool(var, value, -1); + return 0; + } + + return git_color_default_config(var, value, cb); } static int omit_in_dense(struct commit *commit, struct commit **rev, int n) @@ -570,18 +603,25 @@ static int omit_in_dense(struct commit *commit, struct commit **rev, int n) return 0; } -static void parse_reflog_param(const char *arg, int *cnt, const char **base) +static int reflog = 0; + +static int parse_reflog_param(const struct option *opt, const char *arg, + int unset) { char *ep; - *cnt = strtoul(arg, &ep, 10); + const char **base = (const char **)opt->value; + if (!arg) + arg = ""; + reflog = strtoul(arg, &ep, 10); if (*ep == ',') *base = ep + 1; else if (*ep) - die("unrecognized reflog param '%s'", arg + 9); + return error("unrecognized reflog param '%s'", arg); else *base = NULL; - if (*cnt <= 0) - *cnt = DEFAULT_REFLOG; + if (reflog <= 0) + reflog = DEFAULT_REFLOG; + return 0; } int cmd_show_branch(int ac, const char **av, const char *prefix) @@ -607,70 +647,67 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) int head_at = -1; int topics = 0; int dense = 1; - int reflog = 0; const char *reflog_base = NULL; + struct option builtin_show_branch_options[] = { + OPT_BOOLEAN('a', "all", &all_heads, + "show remote-tracking and local branches"), + OPT_BOOLEAN('r', "remotes", &all_remotes, + "show remote-tracking branches"), + OPT_BOOLEAN(0, "color", &showbranch_use_color, + "color '*!+-' corresponding to the branch"), + { OPTION_INTEGER, 0, "more", &extra, "n", + "show <n> more commits after the common ancestor", + PARSE_OPT_OPTARG, NULL, (intptr_t)1 }, + OPT_SET_INT(0, "list", &extra, "synonym to more=-1", -1), + OPT_BOOLEAN(0, "no-name", &no_name, "suppress naming strings"), + OPT_BOOLEAN(0, "current", &with_current_branch, + "include the current branch"), + OPT_BOOLEAN(0, "sha1-name", &sha1_name, + "name commits with their object names"), + OPT_BOOLEAN(0, "merge-base", &merge_base, + "act like git merge-base -a"), + OPT_BOOLEAN(0, "independent", &independent, + "show refs unreachable from any other ref"), + OPT_BOOLEAN(0, "topo-order", &lifo, + "show commits in topological order"), + OPT_BOOLEAN(0, "topics", &topics, + "show only commits not on the first branch"), + OPT_SET_INT(0, "sparse", &dense, + "show merges reachable from only one tip", 0), + OPT_SET_INT(0, "date-order", &lifo, + "show commits where no parent comes before its " + "children", 0), + { OPTION_CALLBACK, 'g', "reflog", &reflog_base, "<n>[,<base>]", + "show <n> most recent ref-log entries starting at " + "base", + PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, + parse_reflog_param }, + OPT_END() + }; git_config(git_show_branch_config, NULL); + if (showbranch_use_color == -1) + showbranch_use_color = git_use_color_default; + /* If nothing is specified, try the default first */ if (ac == 1 && default_num) { ac = default_num + 1; av = default_arg - 1; /* ick; we would not address av[0] */ } - while (1 < ac && av[1][0] == '-') { - const char *arg = av[1]; - if (!strcmp(arg, "--")) { - ac--; av++; - break; - } - else if (!strcmp(arg, "--all") || !strcmp(arg, "-a")) - all_heads = all_remotes = 1; - else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r")) - all_remotes = 1; - else if (!strcmp(arg, "--more")) - extra = 1; - else if (!strcmp(arg, "--list")) - extra = -1; - else if (!strcmp(arg, "--no-name")) - no_name = 1; - else if (!strcmp(arg, "--current")) - with_current_branch = 1; - else if (!strcmp(arg, "--sha1-name")) - sha1_name = 1; - else if (!prefixcmp(arg, "--more=")) - extra = atoi(arg + 7); - else if (!strcmp(arg, "--merge-base")) - merge_base = 1; - else if (!strcmp(arg, "--independent")) - independent = 1; - else if (!strcmp(arg, "--topo-order")) - lifo = 1; - else if (!strcmp(arg, "--topics")) - topics = 1; - else if (!strcmp(arg, "--sparse")) - dense = 0; - else if (!strcmp(arg, "--date-order")) - lifo = 0; - else if (!strcmp(arg, "--reflog") || !strcmp(arg, "-g")) { - reflog = DEFAULT_REFLOG; - } - else if (!prefixcmp(arg, "--reflog=")) - parse_reflog_param(arg + 9, &reflog, &reflog_base); - else if (!prefixcmp(arg, "-g=")) - parse_reflog_param(arg + 3, &reflog, &reflog_base); - else - usage(show_branch_usage); - ac--; av++; - } - ac--; av++; + ac = parse_options(ac, av, prefix, builtin_show_branch_options, + show_branch_usage, PARSE_OPT_STOP_AT_NON_OPTION); + if (all_heads) + all_remotes = 1; if (extra || reflog) { /* "listing" mode is incompatible with * independent nor merge-base modes. */ if (independent || merge_base) - usage(show_branch_usage); + usage_with_options(show_branch_usage, + builtin_show_branch_options); if (reflog && ((0 < extra) || all_heads || all_remotes)) /* * Asking for --more in reflog mode does not @@ -678,7 +715,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) * * Also --all and --remotes do not make sense either. */ - usage(show_branch_usage_reflog); + die("--reflog is incompatible with --all, --remotes, " + "--independent or --merge-base"); } /* If nothing is specified, show all branches by default */ @@ -844,8 +882,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) else { for (j = 0; j < i; j++) putchar(' '); - printf("%c [%s] ", - is_head ? '*' : '!', ref_name[i]); + printf("%s%c%s [%s] ", + get_color_code(i % COLUMN_COLORS_MAX), + is_head ? '*' : '!', + get_color_reset_code(), ref_name[i]); } if (!reflog) { @@ -904,7 +944,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) mark = '*'; else mark = '+'; - putchar(mark); + printf("%s%c%s", + get_color_code(i % COLUMN_COLORS_MAX), + mark, get_color_reset_code()); } putchar(' '); } diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 572b114119..dc76c5090f 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -140,7 +140,7 @@ static int exclude_existing(const char *match) continue; } if (check_ref_format(ref)) { - fprintf(stderr, "warning: ref '%s' ignored\n", ref); + warning("ref '%s' ignored", ref); continue; } if (!string_list_has_string(&existing_refs, ref)) { diff --git a/builtin-symbolic-ref.c b/builtin-symbolic-ref.c index 6ae6bcc0e8..ca855a5eb2 100644 --- a/builtin-symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -36,7 +36,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) }; git_config(git_default_config, NULL); - argc = parse_options(argc, argv, options, git_symbolic_ref_usage, 0); + argc = parse_options(argc, argv, prefix, options, + git_symbolic_ref_usage, 0); if (msg &&!*msg) die("Refusing to perform update with empty message"); switch (argc) { diff --git a/builtin-tag.c b/builtin-tag.c index 01e73747d0..080e04a8fc 100644 --- a/builtin-tag.c +++ b/builtin-tag.c @@ -338,7 +338,7 @@ static void create_tag(const unsigned char *object, const char *tag, exit(128); } if (path) { - unlink(path); + unlink_or_warn(path); free(path); } } @@ -376,8 +376,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct commit_list *with_commit = NULL; struct option options[] = { OPT_BOOLEAN('l', NULL, &list, "list tag names"), - { OPTION_INTEGER, 'n', NULL, &lines, NULL, - "print n lines of each tag message", + { OPTION_INTEGER, 'n', NULL, &lines, "n", + "print <n> lines of each tag message", PARSE_OPT_OPTARG, NULL, 1 }, OPT_BOOLEAN('d', NULL, &delete, "delete tags"), OPT_BOOLEAN('v', NULL, &verify, "verify tags"), @@ -387,7 +387,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) "annotated tag, needs a message"), OPT_CALLBACK('m', NULL, &msg, "msg", "message for the tag", parse_msg_arg), - OPT_STRING('F', NULL, &msgfile, "file", "message in a file"), + OPT_FILENAME('F', NULL, &msgfile, "message in a file"), OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"), OPT_STRING('u', NULL, &keyid, "key-id", "use another key to sign the tag"), @@ -405,8 +405,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) git_config(git_tag_config, NULL); - argc = parse_options(argc, argv, options, git_tag_usage, 0); - msgfile = parse_options_fix_filename(prefix, msgfile); + argc = parse_options(argc, argv, prefix, options, git_tag_usage, 0); if (keyid) { sign = 1; diff --git a/builtin-tar-tree.c b/builtin-tar-tree.c index 0713bca778..f88e721936 100644 --- a/builtin-tar-tree.c +++ b/builtin-tar-tree.c @@ -24,7 +24,7 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix) * git archive --format-tar --prefix=basedir tree-ish */ int i; - const char **nargv = xcalloc(sizeof(*nargv), argc + 2); + const char **nargv = xcalloc(sizeof(*nargv), argc + 3); char *basedir_arg; int nargc = 0; @@ -36,6 +36,13 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix) argv++; argc--; } + + /* + * Because it's just a compatibility wrapper, tar-tree supports only + * the old behaviour of reading attributes from the work tree. + */ + nargv[nargc++] = "--worktree-attributes"; + switch (argc) { default: usage(tar_tree_usage); diff --git a/builtin-unpack-objects.c b/builtin-unpack-objects.c index 9a773239ca..8e831be476 100644 --- a/builtin-unpack-objects.c +++ b/builtin-unpack-objects.c @@ -422,8 +422,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, static void unpack_one(unsigned nr) { unsigned shift; - unsigned char *pack, c; - unsigned long size; + unsigned char *pack; + unsigned long size, c; enum object_type type; obj_list[nr].offset = consumed_bytes; diff --git a/builtin-update-index.c b/builtin-update-index.c index dd43d5bef4..92beaaf4b3 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -195,7 +195,7 @@ static int process_path(const char *path) struct stat st; len = strlen(path); - if (has_symlink_leading_path(len, path)) + if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); /* @@ -292,7 +292,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length) report("add '%s'", path); free_return: if (p < path || p > path + strlen(path)) - free((char*)p); + free((char *)p); } static void read_index_info(int line_termination) @@ -509,7 +509,7 @@ static int do_unresolve(int ac, const char **av, const char *p = prefix_path(prefix, prefix_length, arg); err |= unresolve_one(p); if (p < arg || p > arg + strlen(arg)) - free((char*)p); + free((char *)p); } return err; } @@ -712,7 +712,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (set_executable_bit) chmod_path(set_executable_bit, p); if (p < path || p > path + strlen(path)) - free((char*)p); + free((char *)p); } if (read_from_stdin) { struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT; diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 378dc1b7a6..76ba1d5881 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -23,7 +23,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) }; git_config(git_default_config, NULL); - argc = parse_options(argc, argv, options, git_update_ref_usage, 0); + argc = parse_options(argc, argv, prefix, options, git_update_ref_usage, + 0); if (msg && !*msg) die("Refusing to perform update with empty message."); diff --git a/builtin-upload-archive.c b/builtin-upload-archive.c index f6c4c0f149..c4cd1e1327 100644 --- a/builtin-upload-archive.c +++ b/builtin-upload-archive.c @@ -35,7 +35,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix) strcpy(buf, argv[1]); /* enter-repo smudges its argument */ if (!enter_repo(buf, 0)) - die("not a git archive"); + die("'%s' does not appear to be a git repository", buf); /* put received options in sent_argv[] */ sent_argc = 1; diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c index 729a1593e6..7f7fda42f9 100644 --- a/builtin-verify-tag.c +++ b/builtin-verify-tag.c @@ -55,7 +55,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose) close(gpg.in); ret = finish_command(&gpg); - unlink(path); + unlink_or_warn(path); return ret; } @@ -25,6 +25,7 @@ extern int cmd_add(int argc, const char **argv, const char *prefix); extern int cmd_annotate(int argc, const char **argv, const char *prefix); extern int cmd_apply(int argc, const char **argv, const char *prefix); extern int cmd_archive(int argc, const char **argv, const char *prefix); +extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix); extern int cmd_blame(int argc, const char **argv, const char *prefix); extern int cmd_branch(int argc, const char **argv, const char *prefix); extern int cmd_bundle(int argc, const char **argv, const char *prefix); @@ -71,6 +72,7 @@ extern int cmd_merge_base(int argc, const char **argv, const char *prefix); extern int cmd_merge_ours(int argc, const char **argv, const char *prefix); extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix); +extern int cmd_mktree(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); @@ -98,7 +98,7 @@ int verify_bundle(struct bundle_header *header, int verbose) */ struct ref_list *p = &header->prerequisites; struct rev_info revs; - const char *argv[] = {NULL, "--all"}; + const char *argv[] = {NULL, "--all", NULL}; struct object_array refs; struct commit *commit; int i, ret = 0, req_nr; @@ -140,8 +140,8 @@ struct ondisk_cache_entry_extended { }; struct cache_entry { - unsigned int ce_ctime; - unsigned int ce_mtime; + struct cache_time ce_ctime; + struct cache_time ce_mtime; unsigned int ce_dev; unsigned int ce_ino; unsigned int ce_mode; @@ -282,7 +282,7 @@ struct index_state { struct cache_entry **cache; unsigned int cache_nr, cache_alloc, cache_changed; struct cache_tree *cache_tree; - time_t timestamp; + struct cache_time timestamp; void *alloc; unsigned name_hash_initialized : 1, initialized : 1; @@ -428,7 +428,7 @@ extern int read_index_preload(struct index_state *, const char **pathspec); extern int read_index_from(struct index_state *, const char *path); extern int is_index_unborn(struct index_state *); extern int read_index_unmerged(struct index_state *); -extern int write_index(const struct index_state *, int newfd); +extern int write_index(struct index_state *, int newfd); extern int discard_index(struct index_state *); extern int unmerged_index(const struct index_state *); extern int verify_path(const char *path); @@ -443,6 +443,7 @@ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int opt extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really); extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name); extern int remove_index_entry_at(struct index_state *, int pos); +extern void remove_marked_cache_entries(struct index_state *istate); extern int remove_file_from_index(struct index_state *, const char *path); #define ADD_CACHE_VERBOSE 1 #define ADD_CACHE_PRETEND 2 @@ -541,8 +542,24 @@ enum rebase_setup_type { AUTOREBASE_ALWAYS, }; +enum push_default_type { + PUSH_DEFAULT_UNSPECIFIED = -1, + PUSH_DEFAULT_NOTHING = 0, + PUSH_DEFAULT_MATCHING, + PUSH_DEFAULT_TRACKING, + PUSH_DEFAULT_CURRENT, +}; + extern enum branch_track git_branch_track; extern enum rebase_setup_type autorebase; +extern enum push_default_type push_default; + +enum object_creation_mode { + OBJECT_CREATION_USES_HARDLINKS = 0, + OBJECT_CREATION_USES_RENAMES = 1, +}; + +extern enum object_creation_mode object_creation_mode; #define GIT_REPO_VERSION 0 extern int repository_format_version; @@ -597,6 +614,8 @@ extern int is_empty_blob_sha1(const unsigned char *sha1); int git_mkstemp(char *path, size_t n, const char *template); +int git_mkstemps(char *path, size_t n, const char *template, int suffix_len); + /* * NOTE NOTE NOTE!! * @@ -670,7 +689,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 interpret_branch_name(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[]; @@ -725,11 +744,13 @@ 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 int has_symlink_leading_path(const char *name, int len); +extern int has_symlink_or_noent_leading_path(const char *name, int len); +extern int has_dirs_only_path(const char *name, int len, int prefix_len); +extern void invalidate_lstat_cache(const char *name, int len); extern void clear_lstat_cache(void); +extern void schedule_dir_for_removal(const char *name, int len); +extern void remove_scheduled_dirs(void); extern struct alternate_object_database { struct alternate_object_database *next; @@ -802,7 +823,7 @@ struct ref { #define REF_HEADS (1u << 1) #define REF_TAGS (1u << 2) -extern struct ref *find_ref_by_name(struct ref *list, const char *name); +extern struct ref *find_ref_by_name(const struct ref *list, const char *name); #define CONNECT_VERBOSE (1u << 0) extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); @@ -827,7 +848,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, extern void pack_report(void); extern int open_pack_index(struct packed_git *); -extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); +extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); extern void close_pack_windows(struct packed_git *); extern void unuse_pack(struct pack_window **); extern void free_pack_by_name(const char *); @@ -1,8 +1,6 @@ #include "cache.h" #include "color.h" -#define COLOR_RESET "\033[m" - int git_use_color_default = 0; static int parse_color(const char *name, int len) @@ -54,7 +52,7 @@ void color_parse_mem(const char *value, int value_len, const char *var, int bg = -2; if (!strncasecmp(value, "reset", len)) { - strcpy(dst, "\033[m"); + strcpy(dst, GIT_COLOR_RESET); return; } @@ -175,7 +173,7 @@ static int color_vfprintf(FILE *fp, const char *color, const char *fmt, r += fprintf(fp, "%s", color); r += vfprintf(fp, fmt, args); if (*color) - r += fprintf(fp, "%s", COLOR_RESET); + r += fprintf(fp, "%s", GIT_COLOR_RESET); if (trail) r += fprintf(fp, "%s", trail); return r; @@ -217,7 +215,7 @@ int color_fwrite_lines(FILE *fp, const char *color, 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)) + fputs(GIT_COLOR_RESET, fp) < 0)) return -1; if (!p) return 0; @@ -4,6 +4,17 @@ /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ #define COLOR_MAXLEN 24 +#define GIT_COLOR_NORMAL "" +#define GIT_COLOR_RESET "\033[m" +#define GIT_COLOR_BOLD "\033[1m" +#define GIT_COLOR_RED "\033[31m" +#define GIT_COLOR_GREEN "\033[32m" +#define GIT_COLOR_YELLOW "\033[33m" +#define GIT_COLOR_BLUE "\033[34m" +#define GIT_COLOR_MAGENTA "\033[35m" +#define GIT_COLOR_CYAN "\033[36m" +#define GIT_COLOR_BG_RED "\033[41m" + /* * This variable stores the value of color.ui */ diff --git a/combine-diff.c b/combine-diff.c index 0b071b6e25..60d03676bb 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -24,7 +24,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, path = q->queue[i]->two->path; len = strlen(path); p = xmalloc(combine_diff_path_size(num_parent, len)); - p->path = (char*) &(p->parent[num_parent]); + p->path = (char *) &(p->parent[num_parent]); memcpy(p->path, path, len); p->path[len] = 0; p->len = len; @@ -534,7 +534,6 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, return; /* result deleted */ while (1) { - struct sline *sl = &sline[lno]; unsigned long hunk_end; unsigned long rlines; const char *hunk_comment = NULL; @@ -600,7 +599,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent, struct lline *ll; int j; unsigned long p_mask; - sl = &sline[lno++]; + struct sline *sl = &sline[lno++]; ll = (sl->flag & no_pre_delete) ? NULL : sl->lost_head; while (ll) { fputs(c_old, stdout); @@ -1064,7 +1063,7 @@ void diff_tree_combined_merge(const unsigned char *sha1, for (parents = commit->parents, num_parent = 0; parents; parents = parents->next, num_parent++) - hashcpy((unsigned char*)(parent + num_parent), + hashcpy((unsigned char *)(parent + num_parent), parents->item->object.sha1); diff_tree_combined(sha1, parent, num_parent, dense, rev); } diff --git a/command-list.txt b/command-list.txt index 3583a33ee9..fb03a2ebb5 100644 --- a/command-list.txt +++ b/command-list.txt @@ -33,6 +33,7 @@ git-diff mainporcelain common git-diff-files plumbinginterrogators git-diff-index plumbinginterrogators git-diff-tree plumbinginterrogators +git-difftool ancillaryinterrogators git-fast-export ancillarymanipulators git-fast-import ancillarymanipulators git-fetch mainporcelain common diff --git a/compat/basename.c b/compat/basename.c new file mode 100644 index 0000000000..d8f8a3c6dc --- /dev/null +++ b/compat/basename.c @@ -0,0 +1,15 @@ +#include "../git-compat-util.h" + +/* Adapted from libiberty's basename.c. */ +char *gitbasename (char *path) +{ + const char *base; + /* Skip over the disk name in MSDOS pathnames. */ + if (has_dos_drive_prefix(path)) + path += 2; + for (base = path; *path; path++) { + if (is_dir_sep(*path)) + base = path + 1; + } + return (char *)base; +} diff --git a/compat/cygwin.c b/compat/cygwin.c index ebac148392..b4a51b958c 100644 --- a/compat/cygwin.c +++ b/compat/cygwin.c @@ -89,10 +89,10 @@ static int cygwin_stat(const char *path, struct stat *buf) /* * At start up, we are trying to determine whether Win32 API or cygwin stat * functions should be used. The choice is determined by core.ignorecygwinfstricks. - * Reading this option is not always possible immediately as git_dir may be + * Reading this option is not always possible immediately as git_dir may * not be set yet. So until it is set, use cygwin lstat/stat functions. * However, if core.filemode is set, we must use the Cygwin posix - * stat/lstat as the Windows stat fuctions do not determine posix filemode. + * stat/lstat as the Windows stat functions do not determine posix filemode. * * Note that git_cygwin_config() does NOT call git_default_config() and this * is deliberate. Many commands read from config to establish initial diff --git a/compat/fnmatch/fnmatch.c b/compat/fnmatch/fnmatch.c index 1f4ead5f98..14feac7fe1 100644 --- a/compat/fnmatch/fnmatch.c +++ b/compat/fnmatch/fnmatch.c @@ -39,7 +39,7 @@ # include <stdlib.h> #endif -/* For platform which support the ISO C amendement 1 functionality we +/* For platforms which support the ISO C amendment 1 functionality we support user defined character classes. */ #if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>. */ @@ -90,7 +90,7 @@ # if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H) /* The GNU C library provides support for user-defined character classes - and the functions from ISO C amendement 1. */ + and the functions from ISO C amendment 1. */ # ifdef CHARCLASS_NAME_MAX # define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX # else diff --git a/compat/mingw.c b/compat/mingw.c index 3dbe6a77ff..bed417875e 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1,9 +1,123 @@ #include "../git-compat-util.h" #include "win32.h" +#include <conio.h> #include "../strbuf.h" unsigned int _CRT_fmode = _O_BINARY; +static int err_win_to_posix(DWORD winerr) +{ + int error = ENOSYS; + switch(winerr) { + case ERROR_ACCESS_DENIED: error = EACCES; break; + case ERROR_ACCOUNT_DISABLED: error = EACCES; break; + case ERROR_ACCOUNT_RESTRICTION: error = EACCES; break; + case ERROR_ALREADY_ASSIGNED: error = EBUSY; break; + case ERROR_ALREADY_EXISTS: error = EEXIST; break; + case ERROR_ARITHMETIC_OVERFLOW: error = ERANGE; break; + case ERROR_BAD_COMMAND: error = EIO; break; + case ERROR_BAD_DEVICE: error = ENODEV; break; + case ERROR_BAD_DRIVER_LEVEL: error = ENXIO; break; + case ERROR_BAD_EXE_FORMAT: error = ENOEXEC; break; + case ERROR_BAD_FORMAT: error = ENOEXEC; break; + case ERROR_BAD_LENGTH: error = EINVAL; break; + case ERROR_BAD_PATHNAME: error = ENOENT; break; + case ERROR_BAD_PIPE: error = EPIPE; break; + case ERROR_BAD_UNIT: error = ENODEV; break; + case ERROR_BAD_USERNAME: error = EINVAL; break; + case ERROR_BROKEN_PIPE: error = EPIPE; break; + case ERROR_BUFFER_OVERFLOW: error = ENAMETOOLONG; break; + case ERROR_BUSY: error = EBUSY; break; + case ERROR_BUSY_DRIVE: error = EBUSY; break; + case ERROR_CALL_NOT_IMPLEMENTED: error = ENOSYS; break; + case ERROR_CANNOT_MAKE: error = EACCES; break; + case ERROR_CANTOPEN: error = EIO; break; + case ERROR_CANTREAD: error = EIO; break; + case ERROR_CANTWRITE: error = EIO; break; + case ERROR_CRC: error = EIO; break; + case ERROR_CURRENT_DIRECTORY: error = EACCES; break; + case ERROR_DEVICE_IN_USE: error = EBUSY; break; + case ERROR_DEV_NOT_EXIST: error = ENODEV; break; + case ERROR_DIRECTORY: error = EINVAL; break; + case ERROR_DIR_NOT_EMPTY: error = ENOTEMPTY; break; + case ERROR_DISK_CHANGE: error = EIO; break; + case ERROR_DISK_FULL: error = ENOSPC; break; + case ERROR_DRIVE_LOCKED: error = EBUSY; break; + case ERROR_ENVVAR_NOT_FOUND: error = EINVAL; break; + case ERROR_EXE_MARKED_INVALID: error = ENOEXEC; break; + case ERROR_FILENAME_EXCED_RANGE: error = ENAMETOOLONG; break; + case ERROR_FILE_EXISTS: error = EEXIST; break; + case ERROR_FILE_INVALID: error = ENODEV; break; + case ERROR_FILE_NOT_FOUND: error = ENOENT; break; + case ERROR_GEN_FAILURE: error = EIO; break; + case ERROR_HANDLE_DISK_FULL: error = ENOSPC; break; + case ERROR_INSUFFICIENT_BUFFER: error = ENOMEM; break; + case ERROR_INVALID_ACCESS: error = EACCES; break; + case ERROR_INVALID_ADDRESS: error = EFAULT; break; + case ERROR_INVALID_BLOCK: error = EFAULT; break; + case ERROR_INVALID_DATA: error = EINVAL; break; + case ERROR_INVALID_DRIVE: error = ENODEV; break; + case ERROR_INVALID_EXE_SIGNATURE: error = ENOEXEC; break; + case ERROR_INVALID_FLAGS: error = EINVAL; break; + case ERROR_INVALID_FUNCTION: error = ENOSYS; break; + case ERROR_INVALID_HANDLE: error = EBADF; break; + case ERROR_INVALID_LOGON_HOURS: error = EACCES; break; + case ERROR_INVALID_NAME: error = EINVAL; break; + case ERROR_INVALID_OWNER: error = EINVAL; break; + case ERROR_INVALID_PARAMETER: error = EINVAL; break; + case ERROR_INVALID_PASSWORD: error = EPERM; break; + case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break; + case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break; + case ERROR_INVALID_TARGET_HANDLE: error = EIO; break; + case ERROR_INVALID_WORKSTATION: error = EACCES; break; + case ERROR_IO_DEVICE: error = EIO; break; + case ERROR_IO_INCOMPLETE: error = EINTR; break; + case ERROR_LOCKED: error = EBUSY; break; + case ERROR_LOCK_VIOLATION: error = EACCES; break; + case ERROR_LOGON_FAILURE: error = EACCES; break; + case ERROR_MAPPED_ALIGNMENT: error = EINVAL; break; + case ERROR_META_EXPANSION_TOO_LONG: error = E2BIG; break; + case ERROR_MORE_DATA: error = EPIPE; break; + case ERROR_NEGATIVE_SEEK: error = ESPIPE; break; + case ERROR_NOACCESS: error = EFAULT; break; + case ERROR_NONE_MAPPED: error = EINVAL; break; + case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break; + case ERROR_NOT_READY: error = EAGAIN; break; + case ERROR_NOT_SAME_DEVICE: error = EXDEV; break; + case ERROR_NO_DATA: error = EPIPE; break; + case ERROR_NO_MORE_SEARCH_HANDLES: error = EIO; break; + case ERROR_NO_PROC_SLOTS: error = EAGAIN; break; + case ERROR_NO_SUCH_PRIVILEGE: error = EACCES; break; + case ERROR_OPEN_FAILED: error = EIO; break; + case ERROR_OPEN_FILES: error = EBUSY; break; + case ERROR_OPERATION_ABORTED: error = EINTR; break; + case ERROR_OUTOFMEMORY: error = ENOMEM; break; + case ERROR_PASSWORD_EXPIRED: error = EACCES; break; + case ERROR_PATH_BUSY: error = EBUSY; break; + case ERROR_PATH_NOT_FOUND: error = ENOENT; break; + case ERROR_PIPE_BUSY: error = EBUSY; break; + case ERROR_PIPE_CONNECTED: error = EPIPE; break; + case ERROR_PIPE_LISTENING: error = EPIPE; break; + case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break; + case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break; + case ERROR_READ_FAULT: error = EIO; break; + case ERROR_SEEK: error = EIO; break; + case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break; + case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break; + case ERROR_SHARING_VIOLATION: error = EACCES; break; + case ERROR_STACK_OVERFLOW: error = ENOMEM; break; + case ERROR_SWAPERROR: error = ENOENT; break; + case ERROR_TOO_MANY_MODULES: error = EMFILE; break; + case ERROR_TOO_MANY_OPEN_FILES: error = EMFILE; break; + case ERROR_UNRECOGNIZED_MEDIA: error = ENXIO; break; + case ERROR_UNRECOGNIZED_VOLUME: error = ENODEV; break; + case ERROR_WAIT_NO_CHILDREN: error = ECHILD; break; + case ERROR_WRITE_FAULT: error = EIO; break; + case ERROR_WRITE_PROTECT: error = EROFS; break; + } + return error; +} + #undef open int mingw_open (const char *filename, int oflags, ...) { @@ -46,7 +160,8 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_uid = 0; buf->st_nlink = 1; buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes); - buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ + buf->st_size = fdata.nFileSizeLow | + (((off_t)fdata.nFileSizeHigh)<<32); buf->st_dev = buf->st_rdev = 0; /* not used by Git */ buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); @@ -101,7 +216,7 @@ int mingw_fstat(int fd, struct stat *buf) } /* direct non-file handles to MS's fstat() */ if (GetFileType(fh) != FILE_TYPE_DISK) - return fstat(fd, buf); + return _fstati64(fd, buf); if (GetFileInformationByHandle(fh, &fdata)) { buf->st_ino = 0; @@ -109,7 +224,8 @@ int mingw_fstat(int fd, struct stat *buf) buf->st_uid = 0; buf->st_nlink = 1; buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes); - buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */ + buf->st_size = fdata.nFileSizeLow | + (((off_t)fdata.nFileSizeHigh)<<32); buf->st_dev = buf->st_rdev = 0; /* not used by Git */ buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); @@ -281,7 +397,7 @@ repeat: * its own input data to become available. But since * the process (pack-objects) is itself CPU intensive, * it will happily pick up the time slice that we are - * relinguishing here. + * relinquishing here. */ Sleep(0); goto repeat; @@ -342,7 +458,7 @@ static const char *quote_arg(const char *arg) const char *p = arg; if (!*p) force_quotes = 1; while (*p) { - if (isspace(*p) || *p == '*' || *p == '?' || *p == '{') + if (isspace(*p) || *p == '*' || *p == '?' || *p == '{' || *p == '\'') force_quotes = 1; else if (*p == '"') n++; @@ -410,8 +526,8 @@ static const char *parse_interpreter(const char *cmd) if (buf[0] != '#' || buf[1] != '!') return NULL; buf[n] = '\0'; - p = strchr(buf, '\n'); - if (!p) + p = buf + strcspn(buf, "\r\n"); + if (!*p) return NULL; *p = '\0'; @@ -447,7 +563,7 @@ static char **get_path_split(void) if (!n) return NULL; - path = xmalloc((n+1)*sizeof(char*)); + path = xmalloc((n+1)*sizeof(char *)); p = envpath; i = 0; do { @@ -819,7 +935,9 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz) #undef rename int mingw_rename(const char *pold, const char *pnew) { - DWORD attrs; + DWORD attrs, gle; + int tries = 0; + static const int delay[] = { 0, 1, 10, 20, 40 }; /* * Try native rename() first to get errno right. @@ -829,10 +947,12 @@ int mingw_rename(const char *pold, const char *pnew) return 0; if (errno != EEXIST) return -1; +repeat: if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING)) return 0; /* TODO: translate more errors */ - if (GetLastError() == ERROR_ACCESS_DENIED && + gle = GetLastError(); + if (gle == ERROR_ACCESS_DENIED && (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) { if (attrs & FILE_ATTRIBUTE_DIRECTORY) { errno = EISDIR; @@ -842,10 +962,23 @@ int mingw_rename(const char *pold, const char *pnew) SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) { if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING)) return 0; + gle = GetLastError(); /* revert file attributes on failure */ SetFileAttributes(pnew, attrs); } } + if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) { + /* + * We assume that some other process had the source or + * destination file open at the wrong moment and retry. + * In order to give the other process a higher chance to + * complete its operation, we give up our time slice now. + * If we have to retry again, we do sleep a bit. + */ + Sleep(delay[tries]); + tries++; + goto repeat; + } errno = EACCES; return -1; } @@ -1003,3 +1136,98 @@ void mingw_open_html(const char *unixpath) printf("Launching default browser to display HTML ...\n"); ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0); } + +int link(const char *oldpath, const char *newpath) +{ + typedef BOOL WINAPI (*T)(const char*, const char*, LPSECURITY_ATTRIBUTES); + static T create_hard_link = NULL; + if (!create_hard_link) { + create_hard_link = (T) GetProcAddress( + GetModuleHandle("kernel32.dll"), "CreateHardLinkA"); + if (!create_hard_link) + create_hard_link = (T)-1; + } + if (create_hard_link == (T)-1) { + errno = ENOSYS; + return -1; + } + if (!create_hard_link(newpath, oldpath, NULL)) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + return 0; +} + +char *getpass(const char *prompt) +{ + struct strbuf buf = STRBUF_INIT; + + fputs(prompt, stderr); + for (;;) { + char c = _getch(); + if (c == '\r' || c == '\n') + break; + strbuf_addch(&buf, c); + } + fputs("\n", stderr); + return strbuf_detach(&buf, NULL); +} + +#ifndef NO_MINGW_REPLACE_READDIR +/* MinGW readdir implementation to avoid extra lstats for Git */ +struct mingw_DIR +{ + struct _finddata_t dd_dta; /* disk transfer area for this dir */ + struct mingw_dirent dd_dir; /* Our own implementation, including d_type */ + long dd_handle; /* _findnext handle */ + int dd_stat; /* 0 = next entry to read is first entry, -1 = off the end, positive = 0 based index of next entry */ + char dd_name[1]; /* given path for dir with search pattern (struct is extended) */ +}; + +struct dirent *mingw_readdir(DIR *dir) +{ + WIN32_FIND_DATAA buf; + HANDLE handle; + struct mingw_DIR *mdir = (struct mingw_DIR*)dir; + + if (!dir->dd_handle) { + errno = EBADF; /* No set_errno for mingw */ + return NULL; + } + + if (dir->dd_handle == (long)INVALID_HANDLE_VALUE && dir->dd_stat == 0) + { + handle = FindFirstFileA(dir->dd_name, &buf); + DWORD lasterr = GetLastError(); + dir->dd_handle = (long)handle; + if (handle == INVALID_HANDLE_VALUE && (lasterr != ERROR_NO_MORE_FILES)) { + errno = err_win_to_posix(lasterr); + return NULL; + } + } else if (dir->dd_handle == (long)INVALID_HANDLE_VALUE) { + return NULL; + } else if (!FindNextFileA((HANDLE)dir->dd_handle, &buf)) { + DWORD lasterr = GetLastError(); + FindClose((HANDLE)dir->dd_handle); + dir->dd_handle = (long)INVALID_HANDLE_VALUE; + /* POSIX says you shouldn't set errno when readdir can't + find any more files; so, if another error we leave it set. */ + if (lasterr != ERROR_NO_MORE_FILES) + errno = err_win_to_posix(lasterr); + return NULL; + } + + /* We get here if `buf' contains valid data. */ + strcpy(dir->dd_dir.d_name, buf.cFileName); + ++dir->dd_stat; + + /* Set file type, based on WIN32_FIND_DATA */ + mdir->dd_dir.d_type = 0; + if (buf.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + mdir->dd_dir.d_type |= DT_DIR; + else + mdir->dd_dir.d_type |= DT_REG; + + return (struct dirent*)&dir->dd_dir; +} +#endif // !NO_MINGW_REPLACE_READDIR diff --git a/compat/mingw.h b/compat/mingw.h index a255898801..4f7ba4c13f 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -38,6 +38,8 @@ struct passwd { char *pw_dir; }; +extern char *getpass(const char *prompt); + struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ @@ -67,8 +69,6 @@ static inline int readlink(const char *path, char *buf, size_t bufsiz) { errno = ENOSYS; return -1; } static inline int symlink(const char *oldpath, const char *newpath) { errno = ENOSYS; return -1; } -static inline int link(const char *oldpath, const char *newpath) -{ errno = ENOSYS; return -1; } static inline int fchmod(int fildes, mode_t mode) { errno = ENOSYS; return -1; } static inline int fork(void) @@ -111,7 +111,7 @@ static inline int mingw_unlink(const char *pathname) } #define unlink mingw_unlink -static inline int waitpid(pid_t pid, unsigned *status, unsigned options) +static inline int waitpid(pid_t pid, int *status, unsigned options) { if (options == 0) return _cwait(status, pid, 0); @@ -134,6 +134,7 @@ int getpagesize(void); /* defined in MinGW's libgcc.a */ struct passwd *getpwuid(int uid); int setitimer(int type, struct itimerval *in, struct itimerval *out); int sigaction(int sig, struct sigaction *in, struct sigaction *out); +int link(const char *oldpath, const char *newpath); /* * replacements of existing functions @@ -160,14 +161,22 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz); int mingw_rename(const char*, const char*); #define rename mingw_rename +#ifdef USE_WIN32_MMAP +int mingw_getpagesize(void); +#define getpagesize mingw_getpagesize +#endif + /* Use mingw_lstat() instead of lstat()/stat() and * mingw_fstat() instead of fstat() on Windows. */ +#define off_t off64_t +#define stat _stati64 +#define lseek _lseeki64 int mingw_lstat(const char *file_name, struct stat *buf); int mingw_fstat(int fd, struct stat *buf); #define fstat mingw_fstat #define lstat mingw_lstat -#define stat(x,y) mingw_lstat(x,y) +#define _stati64(x,y) mingw_lstat(x,y) int mingw_utime(const char *file_name, const struct utimbuf *times); #define utime mingw_utime @@ -226,3 +235,32 @@ int main(int argc, const char **argv) \ return mingw_main(argc, argv); \ } \ static int mingw_main(c,v) + +#ifndef NO_MINGW_REPLACE_READDIR +/* + * A replacement of readdir, to ensure that it reads the file type at + * the same time. This avoid extra unneeded lstats in git on MinGW + */ +#undef DT_UNKNOWN +#undef DT_DIR +#undef DT_REG +#undef DT_LNK +#define DT_UNKNOWN 0 +#define DT_DIR 1 +#define DT_REG 2 +#define DT_LNK 3 + +struct mingw_dirent +{ + long d_ino; /* Always zero. */ + union { + unsigned short d_reclen; /* Always zero. */ + unsigned char d_type; /* Reimplementation adds this */ + }; + unsigned short d_namlen; /* Length of name in d_name. */ + char d_name[FILENAME_MAX]; /* File name. */ +}; +#define dirent mingw_dirent +#define readdir(x) mingw_readdir(x) +struct dirent *mingw_readdir(DIR *dir); +#endif // !NO_MINGW_REPLACE_READDIR diff --git a/compat/mkstemps.c b/compat/mkstemps.c new file mode 100644 index 0000000000..14179c8e6d --- /dev/null +++ b/compat/mkstemps.c @@ -0,0 +1,70 @@ +#include "../git-compat-util.h" + +/* Adapted from libiberty's mkstemp.c. */ + +#undef TMP_MAX +#define TMP_MAX 16384 + +int gitmkstemps(char *pattern, int suffix_len) +{ + static const char letters[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"; + static const int num_letters = 62; + uint64_t value; + struct timeval tv; + char *template; + size_t len; + int fd, count; + + len = strlen(pattern); + + if (len < 6 + suffix_len) { + errno = EINVAL; + return -1; + } + + if (strncmp(&pattern[len - 6 - suffix_len], "XXXXXX", 6)) { + errno = EINVAL; + return -1; + } + + /* + * Replace pattern's XXXXXX characters with randomness. + * Try TMP_MAX different filenames. + */ + gettimeofday(&tv, NULL); + value = ((size_t)(tv.tv_usec << 16)) ^ tv.tv_sec ^ getpid(); + template = &pattern[len - 6 - suffix_len]; + for (count = 0; count < TMP_MAX; ++count) { + uint64_t v = value; + /* Fill in the random bits. */ + template[0] = letters[v % num_letters]; v /= num_letters; + template[1] = letters[v % num_letters]; v /= num_letters; + template[2] = letters[v % num_letters]; v /= num_letters; + template[3] = letters[v % num_letters]; v /= num_letters; + template[4] = letters[v % num_letters]; v /= num_letters; + template[5] = letters[v % num_letters]; v /= num_letters; + + fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, 0600); + if (fd > 0) + return fd; + /* + * Fatal error (EPERM, ENOSPC etc). + * It doesn't make sense to loop. + */ + if (errno != EEXIST) + break; + /* + * This is a random value. It is only necessary that + * the next TMP_MAX values generated by adding 7777 to + * VALUE are different with (module 2^32). + */ + value += 7777; + } + /* We return the null string if we can't find a unique file name. */ + pattern[0] = '\0'; + errno = EINVAL; + return -1; +} diff --git a/compat/nedmalloc/License.txt b/compat/nedmalloc/License.txt new file mode 100644 index 0000000000..36b7cd93cd --- /dev/null +++ b/compat/nedmalloc/License.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/compat/nedmalloc/Readme.txt b/compat/nedmalloc/Readme.txt new file mode 100644 index 0000000000..876365646e --- /dev/null +++ b/compat/nedmalloc/Readme.txt @@ -0,0 +1,136 @@ +nedalloc v1.05 15th June 2008: +-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +by Niall Douglas (http://www.nedprod.com/programs/portable/nedmalloc/) + +Enclosed is nedalloc, an alternative malloc implementation for multiple +threads without lock contention based on dlmalloc v2.8.4. It is more +or less a newer implementation of ptmalloc2, the standard allocator in +Linux (which is based on dlmalloc v2.7.0) but also contains a per-thread +cache for maximum CPU scalability. + +It is licensed under the Boost Software License which basically means +you can do anything you like with it. This does not apply to the malloc.c.h +file which remains copyright to others. + +It has been tested on win32 (x86), win64 (x64), Linux (x64), FreeBSD (x64) +and Apple MacOS X (x86). It works very well on all of these and is very +significantly faster than the system allocator on all of these platforms. + +By literally dropping in this allocator as a replacement for your system +allocator, you can see real world improvements of up to three times in normal +code! + +To use: +-=-=-=- +Drop in nedmalloc.h, nedmalloc.c and malloc.c.h into your project. +Configure using the instructions in nedmalloc.h. Run and enjoy. + +To test, compile test.c. It will run a comparison between your system +allocator and nedalloc and tell you how much faster nedalloc is. It also +serves as an example of usage. + +Notes: +-=-=-= +If you want the very latest version of this allocator, get it from the +TnFOX SVN repository at svn://svn.berlios.de/viewcvs/tnfox/trunk/src/nedmalloc + +Because of how nedalloc allocates an mspace per thread, it can cause +severe bloating of memory usage under certain allocation patterns. +You can substantially reduce this wastage by setting MAXTHREADSINPOOL +or the threads parameter to nedcreatepool() to a fraction of the number of +threads which would normally be in a pool at once. This will reduce +bloating at the cost of an increase in lock contention. If allocated size +is less than THREADCACHEMAX, locking is avoided 90-99% of the time and +if most of your allocations are below this value, you can safely set +MAXTHREADSINPOOL to one. + +You will suffer memory leakage unless you call neddisablethreadcache() +per pool for every thread which exits. This is because nedalloc cannot +portably know when a thread exits and thus when its thread cache can +be returned for use by other code. Don't forget pool zero, the system pool. + +For C++ type allocation patterns (where the same sizes of memory are +regularly allocated and deallocated as objects are created and destroyed), +the threadcache always benefits performance. If however your allocation +patterns are different, searching the threadcache may significantly slow +down your code - as a rule of thumb, if cache utilisation is below 80% +(see the source for neddisablethreadcache() for how to enable debug +printing in release mode) then you should disable the thread cache for +that thread. You can compile out the threadcache code by setting +THREADCACHEMAX to zero. + +Speed comparisons: +-=-=-=-=-=-=-=-=-= +See Benchmarks.xls for details. + +The enclosed test.c can do two things: it can be a torture test or a speed +test. The speed test is designed to be a representative synthetic +memory allocator test. It works by randomly mixing allocations with frees +with half of the allocation sizes being a two power multiple less than +512 bytes (to mimic C++ stack instantiated objects) and the other half +being a simple random value less than 16Kb. + +The real world code results are from Tn's TestIO benchmark. This is a +heavily multithreaded and memory intensive benchmark with a lot of branching +and other stuff modern processors don't like so much. As you'll note, the +test doesn't show the benefits of the threadcache mostly due to the saturation +of the memory bus being the limiting factor. + +ChangeLog: +-=-=-=-=-= +v1.05 15th June 2008: + * { 1042 } Added error check for TLSSET() and TLSFREE() macros. Thanks to +Markus Elfring for reporting this. + * { 1043 } Fixed a segfault when freeing memory allocated using +nedindependent_comalloc(). Thanks to Pavel Vozenilek for reporting this. + +v1.04 14th July 2007: + * Fixed a bug with the new optimised implementation that failed to lock +on a realloc under certain conditions. + * Fixed lack of thread synchronisation in InitPool() causing pool corruption + * Fixed a memory leak of thread cache contents on disabling. Thanks to Earl +Chew for reporting this. + * Added a sanity check for freed blocks being valid. + * Reworked test.c into being a torture test. + * Fixed GCC assembler optimisation misspecification + +v1.04alpha_svn915 7th October 2006: + * Fixed failure to unlock thread cache list if allocating a new list failed. +Thanks to Dmitry Chichkov for reporting this. Futher thanks to Aleksey Sanin. + * Fixed realloc(0, <size>) segfaulting. Thanks to Dmitry Chichkov for +reporting this. + * Made config defines #ifndef so they can be overriden by the build system. +Thanks to Aleksey Sanin for suggesting this. + * Fixed deadlock in nedprealloc() due to unnecessary locking of preferred +thread mspace when mspace_realloc() always uses the original block's mspace +anyway. Thanks to Aleksey Sanin for reporting this. + * Made some speed improvements by hacking mspace_malloc() to no longer lock +its mspace, thus allowing the recursive mutex implementation to be removed +with an associated speed increase. Thanks to Aleksey Sanin for suggesting this. + * Fixed a bug where allocating mspaces overran its max limit. Thanks to +Aleksey Sanin for reporting this. + +v1.03 10th July 2006: + * Fixed memory corruption bug in threadcache code which only appeared with >4 +threads and in heavy use of the threadcache. + +v1.02 15th May 2006: + * Integrated dlmalloc v2.8.4, fixing the win32 memory release problem and +improving performance still further. Speed is now up to twice the speed of v1.01 +(average is 67% faster). + * Fixed win32 critical section implementation. Thanks to Pavel Kuznetsov +for reporting this. + * Wasn't locking mspace if all mspaces were locked. Thanks to Pavel Kuznetsov +for reporting this. + * Added Apple Mac OS X support. + +v1.01 24th February 2006: + * Fixed multiprocessor scaling problems by removing sources of cache sloshing + * Earl Chew <earl_chew <at> agilent <dot> com> sent patches for the following: + 1. size2binidx() wasn't working for default code path (non x86) + 2. Fixed failure to release mspace lock under certain circumstances which + caused a deadlock + +v1.00 1st January 2006: + * First release diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h new file mode 100644 index 0000000000..74c42e3162 --- /dev/null +++ b/compat/nedmalloc/malloc.c.h @@ -0,0 +1,5752 @@ +/* + This is a version (aka dlmalloc) of malloc/free/realloc written by + Doug Lea and released to the public domain, as explained at + http://creativecommons.org/licenses/publicdomain. Send questions, + comments, complaints, performance data, etc to dl@cs.oswego.edu + +* Version pre-2.8.4 Mon Nov 27 11:22:37 2006 (dl at gee) + + Note: There may be an updated version of this malloc obtainable at + ftp://gee.cs.oswego.edu/pub/misc/malloc.c + Check before installing! + +* Quickstart + + This library is all in one file to simplify the most common usage: + ftp it, compile it (-O3), and link it into another program. All of + the compile-time options default to reasonable values for use on + most platforms. You might later want to step through various + compile-time and dynamic tuning options. + + For convenience, an include file for code using this malloc is at: + ftp://gee.cs.oswego.edu/pub/misc/malloc-2.8.4.h + You don't really need this .h file unless you call functions not + defined in your system include files. The .h file contains only the + excerpts from this file needed for using this malloc on ANSI C/C++ + systems, so long as you haven't changed compile-time options about + naming and tuning parameters. If you do, then you can create your + own malloc.h that does include all settings by cutting at the point + indicated below. Note that you may already by default be using a C + library containing a malloc that is based on some version of this + malloc (for example in linux). You might still want to use the one + in this file to customize settings or to avoid overheads associated + with library versions. + +* Vital statistics: + + Supported pointer/size_t representation: 4 or 8 bytes + size_t MUST be an unsigned type of the same width as + pointers. (If you are using an ancient system that declares + size_t as a signed type, or need it to be a different width + than pointers, you can use a previous release of this malloc + (e.g. 2.7.2) supporting these.) + + Alignment: 8 bytes (default) + This suffices for nearly all current machines and C compilers. + However, you can define MALLOC_ALIGNMENT to be wider than this + if necessary (up to 128bytes), at the expense of using more space. + + Minimum overhead per allocated chunk: 4 or 8 bytes (if 4byte sizes) + 8 or 16 bytes (if 8byte sizes) + Each malloced chunk has a hidden word of overhead holding size + and status information, and additional cross-check word + if FOOTERS is defined. + + Minimum allocated size: 4-byte ptrs: 16 bytes (including overhead) + 8-byte ptrs: 32 bytes (including overhead) + + Even a request for zero bytes (i.e., malloc(0)) returns a + pointer to something of the minimum allocatable size. + The maximum overhead wastage (i.e., number of extra bytes + allocated than were requested in malloc) is less than or equal + to the minimum size, except for requests >= mmap_threshold that + are serviced via mmap(), where the worst case wastage is about + 32 bytes plus the remainder from a system page (the minimal + mmap unit); typically 4096 or 8192 bytes. + + Security: static-safe; optionally more or less + The "security" of malloc refers to the ability of malicious + code to accentuate the effects of errors (for example, freeing + space that is not currently malloc'ed or overwriting past the + ends of chunks) in code that calls malloc. This malloc + guarantees not to modify any memory locations below the base of + heap, i.e., static variables, even in the presence of usage + errors. The routines additionally detect most improper frees + and reallocs. All this holds as long as the static bookkeeping + for malloc itself is not corrupted by some other means. This + is only one aspect of security -- these checks do not, and + cannot, detect all possible programming errors. + + If FOOTERS is defined nonzero, then each allocated chunk + carries an additional check word to verify that it was malloced + from its space. These check words are the same within each + execution of a program using malloc, but differ across + executions, so externally crafted fake chunks cannot be + freed. This improves security by rejecting frees/reallocs that + could corrupt heap memory, in addition to the checks preventing + writes to statics that are always on. This may further improve + security at the expense of time and space overhead. (Note that + FOOTERS may also be worth using with MSPACES.) + + By default detected errors cause the program to abort (calling + "abort()"). You can override this to instead proceed past + errors by defining PROCEED_ON_ERROR. In this case, a bad free + has no effect, and a malloc that encounters a bad address + caused by user overwrites will ignore the bad address by + dropping pointers and indices to all known memory. This may + be appropriate for programs that should continue if at all + possible in the face of programming errors, although they may + run out of memory because dropped memory is never reclaimed. + + If you don't like either of these options, you can define + CORRUPTION_ERROR_ACTION and USAGE_ERROR_ACTION to do anything + else. And if if you are sure that your program using malloc has + no errors or vulnerabilities, you can define INSECURE to 1, + which might (or might not) provide a small performance improvement. + + Thread-safety: NOT thread-safe unless USE_LOCKS defined + When USE_LOCKS is defined, each public call to malloc, free, + etc is surrounded with either a pthread mutex or a win32 + spinlock (depending on WIN32). This is not especially fast, and + can be a major bottleneck. It is designed only to provide + minimal protection in concurrent environments, and to provide a + basis for extensions. If you are using malloc in a concurrent + program, consider instead using nedmalloc + (http://www.nedprod.com/programs/portable/nedmalloc/) or + ptmalloc (See http://www.malloc.de), which are derived + from versions of this malloc. + + System requirements: Any combination of MORECORE and/or MMAP/MUNMAP + This malloc can use unix sbrk or any emulation (invoked using + the CALL_MORECORE macro) and/or mmap/munmap or any emulation + (invoked using CALL_MMAP/CALL_MUNMAP) to get and release system + memory. On most unix systems, it tends to work best if both + MORECORE and MMAP are enabled. On Win32, it uses emulations + based on VirtualAlloc. It also uses common C library functions + like memset. + + Compliance: I believe it is compliant with the Single Unix Specification + (See http://www.unix.org). Also SVID/XPG, ANSI C, and probably + others as well. + +* Overview of algorithms + + This is not the fastest, most space-conserving, most portable, or + most tunable malloc ever written. However it is among the fastest + while also being among the most space-conserving, portable and + tunable. Consistent balance across these factors results in a good + general-purpose allocator for malloc-intensive programs. + + In most ways, this malloc is a best-fit allocator. Generally, it + chooses the best-fitting existing chunk for a request, with ties + broken in approximately least-recently-used order. (This strategy + normally maintains low fragmentation.) However, for requests less + than 256bytes, it deviates from best-fit when there is not an + exactly fitting available chunk by preferring to use space adjacent + to that used for the previous small request, as well as by breaking + ties in approximately most-recently-used order. (These enhance + locality of series of small allocations.) And for very large requests + (>= 256Kb by default), it relies on system memory mapping + facilities, if supported. (This helps avoid carrying around and + possibly fragmenting memory used only for large chunks.) + + All operations (except malloc_stats and mallinfo) have execution + times that are bounded by a constant factor of the number of bits in + a size_t, not counting any clearing in calloc or copying in realloc, + or actions surrounding MORECORE and MMAP that have times + proportional to the number of non-contiguous regions returned by + system allocation routines, which is often just 1. In real-time + applications, you can optionally suppress segment traversals using + NO_SEGMENT_TRAVERSAL, which assures bounded execution even when + system allocators return non-contiguous spaces, at the typical + expense of carrying around more memory and increased fragmentation. + + The implementation is not very modular and seriously overuses + macros. Perhaps someday all C compilers will do as good a job + inlining modular code as can now be done by brute-force expansion, + but now, enough of them seem not to. + + Some compilers issue a lot of warnings about code that is + dead/unreachable only on some platforms, and also about intentional + uses of negation on unsigned types. All known cases of each can be + ignored. + + For a longer but out of date high-level description, see + http://gee.cs.oswego.edu/dl/html/malloc.html + +* MSPACES + If MSPACES is defined, then in addition to malloc, free, etc., + this file also defines mspace_malloc, mspace_free, etc. These + are versions of malloc routines that take an "mspace" argument + obtained using create_mspace, to control all internal bookkeeping. + If ONLY_MSPACES is defined, only these versions are compiled. + So if you would like to use this allocator for only some allocations, + and your system malloc for others, you can compile with + ONLY_MSPACES and then do something like... + static mspace mymspace = create_mspace(0,0); // for example + #define mymalloc(bytes) mspace_malloc(mymspace, bytes) + + (Note: If you only need one instance of an mspace, you can instead + use "USE_DL_PREFIX" to relabel the global malloc.) + + You can similarly create thread-local allocators by storing + mspaces as thread-locals. For example: + static __thread mspace tlms = 0; + void* tlmalloc(size_t bytes) { + if (tlms == 0) tlms = create_mspace(0, 0); + return mspace_malloc(tlms, bytes); + } + void tlfree(void* mem) { mspace_free(tlms, mem); } + + Unless FOOTERS is defined, each mspace is completely independent. + You cannot allocate from one and free to another (although + conformance is only weakly checked, so usage errors are not always + caught). If FOOTERS is defined, then each chunk carries around a tag + indicating its originating mspace, and frees are directed to their + originating spaces. + + ------------------------- Compile-time options --------------------------- + +Be careful in setting #define values for numerical constants of type +size_t. On some systems, literal values are not automatically extended +to size_t precision unless they are explicitly casted. You can also +use the symbolic values MAX_SIZE_T, SIZE_T_ONE, etc below. + +WIN32 default: defined if _WIN32 defined + Defining WIN32 sets up defaults for MS environment and compilers. + Otherwise defaults are for unix. Beware that there seem to be some + cases where this malloc might not be a pure drop-in replacement for + Win32 malloc: Random-looking failures from Win32 GDI API's (eg; + SetDIBits()) may be due to bugs in some video driver implementations + when pixel buffers are malloc()ed, and the region spans more than + one VirtualAlloc()ed region. Because dlmalloc uses a small (64Kb) + default granularity, pixel buffers may straddle virtual allocation + regions more often than when using the Microsoft allocator. You can + avoid this by using VirtualAlloc() and VirtualFree() for all pixel + buffers rather than using malloc(). If this is not possible, + recompile this malloc with a larger DEFAULT_GRANULARITY. + +MALLOC_ALIGNMENT default: (size_t)8 + Controls the minimum alignment for malloc'ed chunks. It must be a + power of two and at least 8, even on machines for which smaller + alignments would suffice. It may be defined as larger than this + though. Note however that code and data structures are optimized for + the case of 8-byte alignment. + +MSPACES default: 0 (false) + If true, compile in support for independent allocation spaces. + This is only supported if HAVE_MMAP is true. + +ONLY_MSPACES default: 0 (false) + If true, only compile in mspace versions, not regular versions. + +USE_LOCKS default: 0 (false) + Causes each call to each public routine to be surrounded with + pthread or WIN32 mutex lock/unlock. (If set true, this can be + overridden on a per-mspace basis for mspace versions.) If set to a + non-zero value other than 1, locks are used, but their + implementation is left out, so lock functions must be supplied manually. + +USE_SPIN_LOCKS default: 1 iff USE_LOCKS and on x86 using gcc or MSC + If true, uses custom spin locks for locking. This is currently + supported only for x86 platforms using gcc or recent MS compilers. + Otherwise, posix locks or win32 critical sections are used. + +FOOTERS default: 0 + If true, provide extra checking and dispatching by placing + information in the footers of allocated chunks. This adds + space and time overhead. + +INSECURE default: 0 + If true, omit checks for usage errors and heap space overwrites. + +USE_DL_PREFIX default: NOT defined + Causes compiler to prefix all public routines with the string 'dl'. + This can be useful when you only want to use this malloc in one part + of a program, using your regular system malloc elsewhere. + +ABORT default: defined as abort() + Defines how to abort on failed checks. On most systems, a failed + check cannot die with an "assert" or even print an informative + message, because the underlying print routines in turn call malloc, + which will fail again. Generally, the best policy is to simply call + abort(). It's not very useful to do more than this because many + errors due to overwriting will show up as address faults (null, odd + addresses etc) rather than malloc-triggered checks, so will also + abort. Also, most compilers know that abort() does not return, so + can better optimize code conditionally calling it. + +PROCEED_ON_ERROR default: defined as 0 (false) + Controls whether detected bad addresses cause them to bypassed + rather than aborting. If set, detected bad arguments to free and + realloc are ignored. And all bookkeeping information is zeroed out + upon a detected overwrite of freed heap space, thus losing the + ability to ever return it from malloc again, but enabling the + application to proceed. If PROCEED_ON_ERROR is defined, the + static variable malloc_corruption_error_count is compiled in + and can be examined to see if errors have occurred. This option + generates slower code than the default abort policy. + +DEBUG default: NOT defined + The DEBUG setting is mainly intended for people trying to modify + this code or diagnose problems when porting to new platforms. + However, it may also be able to better isolate user errors than just + using runtime checks. The assertions in the check routines spell + out in more detail the assumptions and invariants underlying the + algorithms. The checking is fairly extensive, and will slow down + execution noticeably. Calling malloc_stats or mallinfo with DEBUG + set will attempt to check every non-mmapped allocated and free chunk + in the course of computing the summaries. + +ABORT_ON_ASSERT_FAILURE default: defined as 1 (true) + Debugging assertion failures can be nearly impossible if your + version of the assert macro causes malloc to be called, which will + lead to a cascade of further failures, blowing the runtime stack. + ABORT_ON_ASSERT_FAILURE cause assertions failures to call abort(), + which will usually make debugging easier. + +MALLOC_FAILURE_ACTION default: sets errno to ENOMEM, or no-op on win32 + The action to take before "return 0" when malloc fails to be able to + return memory because there is none available. + +HAVE_MORECORE default: 1 (true) unless win32 or ONLY_MSPACES + True if this system supports sbrk or an emulation of it. + +MORECORE default: sbrk + The name of the sbrk-style system routine to call to obtain more + memory. See below for guidance on writing custom MORECORE + functions. The type of the argument to sbrk/MORECORE varies across + systems. It cannot be size_t, because it supports negative + arguments, so it is normally the signed type of the same width as + size_t (sometimes declared as "intptr_t"). It doesn't much matter + though. Internally, we only call it with arguments less than half + the max value of a size_t, which should work across all reasonable + possibilities, although sometimes generating compiler warnings. + +MORECORE_CONTIGUOUS default: 1 (true) if HAVE_MORECORE + If true, take advantage of fact that consecutive calls to MORECORE + with positive arguments always return contiguous increasing + addresses. This is true of unix sbrk. It does not hurt too much to + set it true anyway, since malloc copes with non-contiguities. + Setting it false when definitely non-contiguous saves time + and possibly wasted space it would take to discover this though. + +MORECORE_CANNOT_TRIM default: NOT defined + True if MORECORE cannot release space back to the system when given + negative arguments. This is generally necessary only if you are + using a hand-crafted MORECORE function that cannot handle negative + arguments. + +NO_SEGMENT_TRAVERSAL default: 0 + If non-zero, suppresses traversals of memory segments + returned by either MORECORE or CALL_MMAP. This disables + merging of segments that are contiguous, and selectively + releasing them to the OS if unused, but bounds execution times. + +HAVE_MMAP default: 1 (true) + True if this system supports mmap or an emulation of it. If so, and + HAVE_MORECORE is not true, MMAP is used for all system + allocation. If set and HAVE_MORECORE is true as well, MMAP is + primarily used to directly allocate very large blocks. It is also + used as a backup strategy in cases where MORECORE fails to provide + space from system. Note: A single call to MUNMAP is assumed to be + able to unmap memory that may have be allocated using multiple calls + to MMAP, so long as they are adjacent. + +HAVE_MREMAP default: 1 on linux, else 0 + If true realloc() uses mremap() to re-allocate large blocks and + extend or shrink allocation spaces. + +MMAP_CLEARS default: 1 except on WINCE. + True if mmap clears memory so calloc doesn't need to. This is true + for standard unix mmap using /dev/zero and on WIN32 except for WINCE. + +USE_BUILTIN_FFS default: 0 (i.e., not used) + Causes malloc to use the builtin ffs() function to compute indices. + Some compilers may recognize and intrinsify ffs to be faster than the + supplied C version. Also, the case of x86 using gcc is special-cased + to an asm instruction, so is already as fast as it can be, and so + this setting has no effect. Similarly for Win32 under recent MS compilers. + (On most x86s, the asm version is only slightly faster than the C version.) + +malloc_getpagesize default: derive from system includes, or 4096. + The system page size. To the extent possible, this malloc manages + memory from the system in page-size units. This may be (and + usually is) a function rather than a constant. This is ignored + if WIN32, where page size is determined using getSystemInfo during + initialization. + +USE_DEV_RANDOM default: 0 (i.e., not used) + Causes malloc to use /dev/random to initialize secure magic seed for + stamping footers. Otherwise, the current time is used. + +NO_MALLINFO default: 0 + If defined, don't compile "mallinfo". This can be a simple way + of dealing with mismatches between system declarations and + those in this file. + +MALLINFO_FIELD_TYPE default: size_t + The type of the fields in the mallinfo struct. This was originally + defined as "int" in SVID etc, but is more usefully defined as + size_t. The value is used only if HAVE_USR_INCLUDE_MALLOC_H is not set + +REALLOC_ZERO_BYTES_FREES default: not defined + This should be set if a call to realloc with zero bytes should + be the same as a call to free. Some people think it should. Otherwise, + since this malloc returns a unique pointer for malloc(0), so does + realloc(p, 0). + +LACKS_UNISTD_H, LACKS_FCNTL_H, LACKS_SYS_PARAM_H, LACKS_SYS_MMAN_H +LACKS_STRINGS_H, LACKS_STRING_H, LACKS_SYS_TYPES_H, LACKS_ERRNO_H +LACKS_STDLIB_H default: NOT defined unless on WIN32 + Define these if your system does not have these header files. + You might need to manually insert some of the declarations they provide. + +DEFAULT_GRANULARITY default: page size if MORECORE_CONTIGUOUS, + system_info.dwAllocationGranularity in WIN32, + otherwise 64K. + Also settable using mallopt(M_GRANULARITY, x) + The unit for allocating and deallocating memory from the system. On + most systems with contiguous MORECORE, there is no reason to + make this more than a page. However, systems with MMAP tend to + either require or encourage larger granularities. You can increase + this value to prevent system allocation functions to be called so + often, especially if they are slow. The value must be at least one + page and must be a power of two. Setting to 0 causes initialization + to either page size or win32 region size. (Note: In previous + versions of malloc, the equivalent of this option was called + "TOP_PAD") + +DEFAULT_TRIM_THRESHOLD default: 2MB + Also settable using mallopt(M_TRIM_THRESHOLD, x) + The maximum amount of unused top-most memory to keep before + releasing via malloc_trim in free(). Automatic trimming is mainly + useful in long-lived programs using contiguous MORECORE. Because + trimming via sbrk can be slow on some systems, and can sometimes be + wasteful (in cases where programs immediately afterward allocate + more large chunks) the value should be high enough so that your + overall system performance would improve by releasing this much + memory. As a rough guide, you might set to a value close to the + average size of a process (program) running on your system. + Releasing this much memory would allow such a process to run in + memory. Generally, it is worth tuning trim thresholds when a + program undergoes phases where several large chunks are allocated + and released in ways that can reuse each other's storage, perhaps + mixed with phases where there are no such chunks at all. The trim + value must be greater than page size to have any useful effect. To + disable trimming completely, you can set to MAX_SIZE_T. Note that the trick + some people use of mallocing a huge space and then freeing it at + program startup, in an attempt to reserve system memory, doesn't + have the intended effect under automatic trimming, since that memory + will immediately be returned to the system. + +DEFAULT_MMAP_THRESHOLD default: 256K + Also settable using mallopt(M_MMAP_THRESHOLD, x) + The request size threshold for using MMAP to directly service a + request. Requests of at least this size that cannot be allocated + using already-existing space will be serviced via mmap. (If enough + normal freed space already exists it is used instead.) Using mmap + segregates relatively large chunks of memory so that they can be + individually obtained and released from the host system. A request + serviced through mmap is never reused by any other request (at least + not directly; the system may just so happen to remap successive + requests to the same locations). Segregating space in this way has + the benefits that: Mmapped space can always be individually released + back to the system, which helps keep the system level memory demands + of a long-lived program low. Also, mapped memory doesn't become + `locked' between other chunks, as can happen with normally allocated + chunks, which means that even trimming via malloc_trim would not + release them. However, it has the disadvantage that the space + cannot be reclaimed, consolidated, and then used to service later + requests, as happens with normal chunks. The advantages of mmap + nearly always outweigh disadvantages for "large" chunks, but the + value of "large" may vary across systems. The default is an + empirically derived value that works well in most systems. You can + disable mmap by setting to MAX_SIZE_T. + +MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP + The number of consolidated frees between checks to release + unused segments when freeing. When using non-contiguous segments, + especially with multiple mspaces, checking only for topmost space + doesn't always suffice to trigger trimming. To compensate for this, + free() will, with a period of MAX_RELEASE_CHECK_RATE (or the + current number of segments, if greater) try to release unused + segments to the OS when freeing chunks that result in + consolidation. The best value for this parameter is a compromise + between slowing down frees with relatively costly checks that + rarely trigger versus holding on to unused memory. To effectively + disable, set to MAX_SIZE_T. This may lead to a very slight speed + improvement at the expense of carrying around more memory. +*/ + +/* Version identifier to allow people to support multiple versions */ +#ifndef DLMALLOC_VERSION +#define DLMALLOC_VERSION 20804 +#endif /* DLMALLOC_VERSION */ + +#ifndef WIN32 +#ifdef _WIN32 +#define WIN32 1 +#endif /* _WIN32 */ +#ifdef _WIN32_WCE +#define LACKS_FCNTL_H +#define WIN32 1 +#endif /* _WIN32_WCE */ +#endif /* WIN32 */ +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#define _WIN32_WINNT 0x403 +#include <windows.h> +#define HAVE_MMAP 1 +#define HAVE_MORECORE 0 +#define LACKS_UNISTD_H +#define LACKS_SYS_PARAM_H +#define LACKS_SYS_MMAN_H +#define LACKS_STRING_H +#define LACKS_STRINGS_H +#define LACKS_SYS_TYPES_H +#define LACKS_ERRNO_H +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION +#endif /* MALLOC_FAILURE_ACTION */ +#ifdef _WIN32_WCE /* WINCE reportedly does not clear */ +#define MMAP_CLEARS 0 +#else +#define MMAP_CLEARS 1 +#endif /* _WIN32_WCE */ +#endif /* WIN32 */ + +#if defined(DARWIN) || defined(_DARWIN) +/* Mac OSX docs advise not to use sbrk; it seems better to use mmap */ +#ifndef HAVE_MORECORE +#define HAVE_MORECORE 0 +#define HAVE_MMAP 1 +/* OSX allocators provide 16 byte alignment */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)16U) +#endif +#endif /* HAVE_MORECORE */ +#endif /* DARWIN */ + +#ifndef LACKS_SYS_TYPES_H +#include <sys/types.h> /* For size_t */ +#endif /* LACKS_SYS_TYPES_H */ + +/* The maximum possible size_t value has all bits set */ +#define MAX_SIZE_T (~(size_t)0) + +#ifndef ONLY_MSPACES +#define ONLY_MSPACES 0 /* define to a value */ +#else +#define ONLY_MSPACES 1 +#endif /* ONLY_MSPACES */ +#ifndef MSPACES +#if ONLY_MSPACES +#define MSPACES 1 +#else /* ONLY_MSPACES */ +#define MSPACES 0 +#endif /* ONLY_MSPACES */ +#endif /* MSPACES */ +#ifndef MALLOC_ALIGNMENT +#define MALLOC_ALIGNMENT ((size_t)8U) +#endif /* MALLOC_ALIGNMENT */ +#ifndef FOOTERS +#define FOOTERS 0 +#endif /* FOOTERS */ +#ifndef ABORT +#define ABORT abort() +#endif /* ABORT */ +#ifndef ABORT_ON_ASSERT_FAILURE +#define ABORT_ON_ASSERT_FAILURE 1 +#endif /* ABORT_ON_ASSERT_FAILURE */ +#ifndef PROCEED_ON_ERROR +#define PROCEED_ON_ERROR 0 +#endif /* PROCEED_ON_ERROR */ +#ifndef USE_LOCKS +#define USE_LOCKS 0 +#endif /* USE_LOCKS */ +#ifndef USE_SPIN_LOCKS +#if USE_LOCKS && (defined(__GNUC__) && ((defined(__i386__) || defined(__x86_64__)))) || (defined(_MSC_VER) && _MSC_VER>=1310) +#define USE_SPIN_LOCKS 1 +#else +#define USE_SPIN_LOCKS 0 +#endif /* USE_LOCKS && ... */ +#endif /* USE_SPIN_LOCKS */ +#ifndef INSECURE +#define INSECURE 0 +#endif /* INSECURE */ +#ifndef HAVE_MMAP +#define HAVE_MMAP 1 +#endif /* HAVE_MMAP */ +#ifndef MMAP_CLEARS +#define MMAP_CLEARS 1 +#endif /* MMAP_CLEARS */ +#ifndef HAVE_MREMAP +#ifdef linux +#define HAVE_MREMAP 1 +#else /* linux */ +#define HAVE_MREMAP 0 +#endif /* linux */ +#endif /* HAVE_MREMAP */ +#ifndef MALLOC_FAILURE_ACTION +#define MALLOC_FAILURE_ACTION errno = ENOMEM; +#endif /* MALLOC_FAILURE_ACTION */ +#ifndef HAVE_MORECORE +#if ONLY_MSPACES +#define HAVE_MORECORE 0 +#else /* ONLY_MSPACES */ +#define HAVE_MORECORE 1 +#endif /* ONLY_MSPACES */ +#endif /* HAVE_MORECORE */ +#if !HAVE_MORECORE +#define MORECORE_CONTIGUOUS 0 +#else /* !HAVE_MORECORE */ +#define MORECORE_DEFAULT sbrk +#ifndef MORECORE_CONTIGUOUS +#define MORECORE_CONTIGUOUS 1 +#endif /* MORECORE_CONTIGUOUS */ +#endif /* HAVE_MORECORE */ +#ifndef DEFAULT_GRANULARITY +#if (MORECORE_CONTIGUOUS || defined(WIN32)) +#define DEFAULT_GRANULARITY (0) /* 0 means to compute in init_mparams */ +#else /* MORECORE_CONTIGUOUS */ +#define DEFAULT_GRANULARITY ((size_t)64U * (size_t)1024U) +#endif /* MORECORE_CONTIGUOUS */ +#endif /* DEFAULT_GRANULARITY */ +#ifndef DEFAULT_TRIM_THRESHOLD +#ifndef MORECORE_CANNOT_TRIM +#define DEFAULT_TRIM_THRESHOLD ((size_t)2U * (size_t)1024U * (size_t)1024U) +#else /* MORECORE_CANNOT_TRIM */ +#define DEFAULT_TRIM_THRESHOLD MAX_SIZE_T +#endif /* MORECORE_CANNOT_TRIM */ +#endif /* DEFAULT_TRIM_THRESHOLD */ +#ifndef DEFAULT_MMAP_THRESHOLD +#if HAVE_MMAP +#define DEFAULT_MMAP_THRESHOLD ((size_t)256U * (size_t)1024U) +#else /* HAVE_MMAP */ +#define DEFAULT_MMAP_THRESHOLD MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* DEFAULT_MMAP_THRESHOLD */ +#ifndef MAX_RELEASE_CHECK_RATE +#if HAVE_MMAP +#define MAX_RELEASE_CHECK_RATE 4095 +#else +#define MAX_RELEASE_CHECK_RATE MAX_SIZE_T +#endif /* HAVE_MMAP */ +#endif /* MAX_RELEASE_CHECK_RATE */ +#ifndef USE_BUILTIN_FFS +#define USE_BUILTIN_FFS 0 +#endif /* USE_BUILTIN_FFS */ +#ifndef USE_DEV_RANDOM +#define USE_DEV_RANDOM 0 +#endif /* USE_DEV_RANDOM */ +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif /* NO_MALLINFO */ +#ifndef MALLINFO_FIELD_TYPE +#define MALLINFO_FIELD_TYPE size_t +#endif /* MALLINFO_FIELD_TYPE */ +#ifndef NO_SEGMENT_TRAVERSAL +#define NO_SEGMENT_TRAVERSAL 0 +#endif /* NO_SEGMENT_TRAVERSAL */ + +/* + mallopt tuning options. SVID/XPG defines four standard parameter + numbers for mallopt, normally defined in malloc.h. None of these + are used in this malloc, so setting them has no effect. But this + malloc does support the following options. +*/ + +#define M_TRIM_THRESHOLD (-1) +#define M_GRANULARITY (-2) +#define M_MMAP_THRESHOLD (-3) + +/* ------------------------ Mallinfo declarations ------------------------ */ + +#if !NO_MALLINFO +/* + This version of malloc supports the standard SVID/XPG mallinfo + routine that returns a struct containing usage properties and + statistics. It should work on any system that has a + /usr/include/malloc.h defining struct mallinfo. The main + declaration needed is the mallinfo struct that is returned (by-copy) + by mallinfo(). The malloinfo struct contains a bunch of fields that + are not even meaningful in this version of malloc. These fields are + are instead filled by mallinfo() with other numbers that might be of + interest. + + HAVE_USR_INCLUDE_MALLOC_H should be set if you have a + /usr/include/malloc.h file that includes a declaration of struct + mallinfo. If so, it is included; else a compliant version is + declared below. These must be precisely the same for mallinfo() to + work. The original SVID version of this struct, defined on most + systems with mallinfo, declares all fields as ints. But some others + define as unsigned long. If your system defines the fields using a + type of different width than listed here, you MUST #include your + system version and #define HAVE_USR_INCLUDE_MALLOC_H. +*/ + +/* #define HAVE_USR_INCLUDE_MALLOC_H */ + +#ifdef HAVE_USR_INCLUDE_MALLOC_H +#include "/usr/include/malloc.h" +#else /* HAVE_USR_INCLUDE_MALLOC_H */ +#ifndef STRUCT_MALLINFO_DECLARED +#define STRUCT_MALLINFO_DECLARED 1 +struct mallinfo { + MALLINFO_FIELD_TYPE arena; /* non-mmapped space allocated from system */ + MALLINFO_FIELD_TYPE ordblks; /* number of free chunks */ + MALLINFO_FIELD_TYPE smblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblks; /* always 0 */ + MALLINFO_FIELD_TYPE hblkhd; /* space in mmapped regions */ + MALLINFO_FIELD_TYPE usmblks; /* maximum total allocated space */ + MALLINFO_FIELD_TYPE fsmblks; /* always 0 */ + MALLINFO_FIELD_TYPE uordblks; /* total allocated space */ + MALLINFO_FIELD_TYPE fordblks; /* total free space */ + MALLINFO_FIELD_TYPE keepcost; /* releasable (via malloc_trim) space */ +}; +#endif /* STRUCT_MALLINFO_DECLARED */ +#endif /* HAVE_USR_INCLUDE_MALLOC_H */ +#endif /* NO_MALLINFO */ + +/* + Try to persuade compilers to inline. The most critical functions for + inlining are defined as macros, so these aren't used for them. +*/ + +#ifndef FORCEINLINE + #if defined(__GNUC__) +#define FORCEINLINE __inline __attribute__ ((always_inline)) + #elif defined(_MSC_VER) + #define FORCEINLINE __forceinline + #endif +#endif +#ifndef NOINLINE + #if defined(__GNUC__) + #define NOINLINE __attribute__ ((noinline)) + #elif defined(_MSC_VER) + #define NOINLINE __declspec(noinline) + #else + #define NOINLINE + #endif +#endif + +#ifdef __cplusplus +extern "C" { +#ifndef FORCEINLINE + #define FORCEINLINE inline +#endif +#endif /* __cplusplus */ +#ifndef FORCEINLINE + #define FORCEINLINE +#endif + +#if !ONLY_MSPACES + +/* ------------------- Declarations of public routines ------------------- */ + +#ifndef USE_DL_PREFIX +#define dlcalloc calloc +#define dlfree free +#define dlmalloc malloc +#define dlmemalign memalign +#define dlrealloc realloc +#define dlvalloc valloc +#define dlpvalloc pvalloc +#define dlmallinfo mallinfo +#define dlmallopt mallopt +#define dlmalloc_trim malloc_trim +#define dlmalloc_stats malloc_stats +#define dlmalloc_usable_size malloc_usable_size +#define dlmalloc_footprint malloc_footprint +#define dlmalloc_max_footprint malloc_max_footprint +#define dlindependent_calloc independent_calloc +#define dlindependent_comalloc independent_comalloc +#endif /* USE_DL_PREFIX */ + + +/* + malloc(size_t n) + Returns a pointer to a newly allocated chunk of at least n bytes, or + null if no space is available, in which case errno is set to ENOMEM + on ANSI C systems. + + If n is zero, malloc returns a minimum-sized chunk. (The minimum + size is 16 bytes on most 32bit systems, and 32 bytes on 64bit + systems.) Note that size_t is an unsigned type, so calls with + arguments that would be negative if signed are interpreted as + requests for huge amounts of space, which will often fail. The + maximum supported value of n differs across systems, but is in all + cases less than the maximum representable value of a size_t. +*/ +void* dlmalloc(size_t); + +/* + free(void* p) + Releases the chunk of memory pointed to by p, that had been previously + allocated using malloc or a related routine such as realloc. + It has no effect if p is null. If p was not malloced or already + freed, free(p) will by default cause the current program to abort. +*/ +void dlfree(void*); + +/* + calloc(size_t n_elements, size_t element_size); + Returns a pointer to n_elements * element_size bytes, with all locations + set to zero. +*/ +void* dlcalloc(size_t, size_t); + +/* + realloc(void* p, size_t n) + Returns a pointer to a chunk of size n that contains the same data + as does chunk p up to the minimum of (n, p's size) bytes, or null + if no space is available. + + The returned pointer may or may not be the same as p. The algorithm + prefers extending p in most cases when possible, otherwise it + employs the equivalent of a malloc-copy-free sequence. + + If p is null, realloc is equivalent to malloc. + + If space is not available, realloc returns null, errno is set (if on + ANSI) and p is NOT freed. + + if n is for fewer bytes than already held by p, the newly unused + space is lopped off and freed if possible. realloc with a size + argument of zero (re)allocates a minimum-sized chunk. + + The old unix realloc convention of allowing the last-free'd chunk + to be used as an argument to realloc is not supported. +*/ + +void* dlrealloc(void*, size_t); + +/* + memalign(size_t alignment, size_t n); + Returns a pointer to a newly allocated chunk of n bytes, aligned + in accord with the alignment argument. + + The alignment argument should be a power of two. If the argument is + not a power of two, the nearest greater power is used. + 8-byte alignment is guaranteed by normal malloc calls, so don't + bother calling memalign with an argument of 8 or less. + + Overreliance on memalign is a sure way to fragment space. +*/ +void* dlmemalign(size_t, size_t); + +/* + valloc(size_t n); + Equivalent to memalign(pagesize, n), where pagesize is the page + size of the system. If the pagesize is unknown, 4096 is used. +*/ +void* dlvalloc(size_t); + +/* + mallopt(int parameter_number, int parameter_value) + Sets tunable parameters The format is to provide a + (parameter-number, parameter-value) pair. mallopt then sets the + corresponding parameter to the argument value if it can (i.e., so + long as the value is meaningful), and returns 1 if successful else + 0. To workaround the fact that mallopt is specified to use int, + not size_t parameters, the value -1 is specially treated as the + maximum unsigned size_t value. + + SVID/XPG/ANSI defines four standard param numbers for mallopt, + normally defined in malloc.h. None of these are use in this malloc, + so setting them has no effect. But this malloc also supports other + options in mallopt. See below for details. Briefly, supported + parameters are as follows (listed defaults are for "typical" + configurations). + + Symbol param # default allowed param values + M_TRIM_THRESHOLD -1 2*1024*1024 any (-1 disables) + M_GRANULARITY -2 page size any power of 2 >= page size + M_MMAP_THRESHOLD -3 256*1024 any (or 0 if no MMAP support) +*/ +int dlmallopt(int, int); + +/* + malloc_footprint(); + Returns the number of bytes obtained from the system. The total + number of bytes allocated by malloc, realloc etc., is less than this + value. Unlike mallinfo, this function returns only a precomputed + result, so can be called frequently to monitor memory consumption. + Even if locks are otherwise defined, this function does not use them, + so results might not be up to date. +*/ +size_t dlmalloc_footprint(void); + +/* + malloc_max_footprint(); + Returns the maximum number of bytes obtained from the system. This + value will be greater than current footprint if deallocated space + has been reclaimed by the system. The peak number of bytes allocated + by malloc, realloc etc., is less than this value. Unlike mallinfo, + this function returns only a precomputed result, so can be called + frequently to monitor memory consumption. Even if locks are + otherwise defined, this function does not use them, so results might + not be up to date. +*/ +size_t dlmalloc_max_footprint(void); + +#if !NO_MALLINFO +/* + mallinfo() + Returns (by copy) a struct containing various summary statistics: + + arena: current total non-mmapped bytes allocated from system + ordblks: the number of free chunks + smblks: always zero. + hblks: current number of mmapped regions + hblkhd: total bytes held in mmapped regions + usmblks: the maximum total allocated space. This will be greater + than current total if trimming has occurred. + fsmblks: always zero + uordblks: current total allocated space (normal or mmapped) + fordblks: total free space + keepcost: the maximum number of bytes that could ideally be released + back to system via malloc_trim. ("ideally" means that + it ignores page restrictions etc.) + + Because these fields are ints, but internal bookkeeping may + be kept as longs, the reported values may wrap around zero and + thus be inaccurate. +*/ +struct mallinfo dlmallinfo(void); +#endif /* NO_MALLINFO */ + +/* + independent_calloc(size_t n_elements, size_t element_size, void* chunks[]); + + independent_calloc is similar to calloc, but instead of returning a + single cleared space, it returns an array of pointers to n_elements + independent elements that can hold contents of size elem_size, each + of which starts out cleared, and can be independently freed, + realloc'ed etc. The elements are guaranteed to be adjacently + allocated (this is not guaranteed to occur with multiple callocs or + mallocs), which may also improve cache locality in some + applications. + + The "chunks" argument is optional (i.e., may be null, which is + probably the most typical usage). If it is null, the returned array + is itself dynamically allocated and should also be freed when it is + no longer needed. Otherwise, the chunks array must be of at least + n_elements in length. It is filled in with the pointers to the + chunks. + + In either case, independent_calloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and "chunks" + is null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use regular calloc and assign pointers into this + space to represent elements. (In this case though, you cannot + independently free elements.) + + independent_calloc simplifies and speeds up implementations of many + kinds of pools. It may also be useful when constructing large data + structures that initially have a fixed number of fixed-sized nodes, + but the number is not known at compile time, and some of the nodes + may later need to be freed. For example: + + struct Node { int item; struct Node* next; }; + + struct Node* build_list() { + struct Node** pool; + int n = read_number_of_nodes_needed(); + if (n <= 0) return 0; + pool = (struct Node**)(independent_calloc(n, sizeof(struct Node), 0); + if (pool == 0) die(); + // organize into a linked list... + struct Node* first = pool[0]; + for (i = 0; i < n-1; ++i) + pool[i]->next = pool[i+1]; + free(pool); // Can now free the array (or not, if it is needed later) + return first; + } +*/ +void** dlindependent_calloc(size_t, size_t, void**); + +/* + independent_comalloc(size_t n_elements, size_t sizes[], void* chunks[]); + + independent_comalloc allocates, all at once, a set of n_elements + chunks with sizes indicated in the "sizes" array. It returns + an array of pointers to these elements, each of which can be + independently freed, realloc'ed etc. The elements are guaranteed to + be adjacently allocated (this is not guaranteed to occur with + multiple callocs or mallocs), which may also improve cache locality + in some applications. + + The "chunks" argument is optional (i.e., may be null). If it is null + the returned array is itself dynamically allocated and should also + be freed when it is no longer needed. Otherwise, the chunks array + must be of at least n_elements in length. It is filled in with the + pointers to the chunks. + + In either case, independent_comalloc returns this pointer array, or + null if the allocation failed. If n_elements is zero and chunks is + null, it returns a chunk representing an array with zero elements + (which should be freed if not wanted). + + Each element must be individually freed when it is no longer + needed. If you'd like to instead be able to free all at once, you + should instead use a single regular malloc, and assign pointers at + particular offsets in the aggregate space. (In this case though, you + cannot independently free elements.) + + independent_comallac differs from independent_calloc in that each + element may have a different size, and also that it does not + automatically clear elements. + + independent_comalloc can be used to speed up allocation in cases + where several structs or objects must always be allocated at the + same time. For example: + + struct Head { ... } + struct Foot { ... } + + void send_message(char* msg) { + int msglen = strlen(msg); + size_t sizes[3] = { sizeof(struct Head), msglen, sizeof(struct Foot) }; + void* chunks[3]; + if (independent_comalloc(3, sizes, chunks) == 0) + die(); + struct Head* head = (struct Head*)(chunks[0]); + char* body = (char*)(chunks[1]); + struct Foot* foot = (struct Foot*)(chunks[2]); + // ... + } + + In general though, independent_comalloc is worth using only for + larger values of n_elements. For small values, you probably won't + detect enough difference from series of malloc calls to bother. + + Overuse of independent_comalloc can increase overall memory usage, + since it cannot reuse existing noncontiguous small chunks that + might be available for some of the elements. +*/ +void** dlindependent_comalloc(size_t, size_t*, void**); + + +/* + pvalloc(size_t n); + Equivalent to valloc(minimum-page-that-holds(n)), that is, + round up n to nearest pagesize. + */ +void* dlpvalloc(size_t); + +/* + malloc_trim(size_t pad); + + If possible, gives memory back to the system (via negative arguments + to sbrk) if there is unused memory at the `high' end of the malloc + pool or in unused MMAP segments. You can call this after freeing + large blocks of memory to potentially reduce the system-level memory + requirements of a program. However, it cannot guarantee to reduce + memory. Under some allocation patterns, some large free blocks of + memory will be locked between two used chunks, so they cannot be + given back to the system. + + The `pad' argument to malloc_trim represents the amount of free + trailing space to leave untrimmed. If this argument is zero, only + the minimum amount of memory to maintain internal data structures + will be left. Non-zero arguments can be supplied to maintain enough + trailing space to service future expected allocations without having + to re-obtain memory from the system. + + Malloc_trim returns 1 if it actually released any memory, else 0. +*/ +int dlmalloc_trim(size_t); + +/* + malloc_stats(); + Prints on stderr the amount of space obtained from the system (both + via sbrk and mmap), the maximum amount (which may be more than + current if malloc_trim and/or munmap got called), and the current + number of bytes allocated via malloc (or realloc, etc) but not yet + freed. Note that this is the number of bytes allocated, not the + number requested. It will be larger than the number requested + because of alignment and bookkeeping overhead. Because it includes + alignment wastage as being in use, this figure may be greater than + zero even when no user-level chunks are allocated. + + The reported current and maximum system memory can be inaccurate if + a program makes other calls to system memory allocation functions + (normally sbrk) outside of malloc. + + malloc_stats prints only the most commonly interesting statistics. + More information can be obtained by calling mallinfo. +*/ +void dlmalloc_stats(void); + +#endif /* ONLY_MSPACES */ + +/* + malloc_usable_size(void* p); + + Returns the number of bytes you can actually use in + an allocated chunk, which may be more than you requested (although + often not) due to alignment and minimum size constraints. + You can use this many bytes without worrying about + overwriting other allocated objects. This is not a particularly great + programming practice. malloc_usable_size can be more useful in + debugging and assertions, for example: + + p = malloc(n); + assert(malloc_usable_size(p) >= 256); +*/ +size_t dlmalloc_usable_size(void*); + + +#if MSPACES + +/* + mspace is an opaque type representing an independent + region of space that supports mspace_malloc, etc. +*/ +typedef void* mspace; + +/* + create_mspace creates and returns a new independent space with the + given initial capacity, or, if 0, the default granularity size. It + returns null if there is no system memory available to create the + space. If argument locked is non-zero, the space uses a separate + lock to control access. The capacity of the space will grow + dynamically as needed to service mspace_malloc requests. You can + control the sizes of incremental increases of this space by + compiling with a different DEFAULT_GRANULARITY or dynamically + setting with mallopt(M_GRANULARITY, value). +*/ +mspace create_mspace(size_t capacity, int locked); + +/* + destroy_mspace destroys the given space, and attempts to return all + of its memory back to the system, returning the total number of + bytes freed. After destruction, the results of access to all memory + used by the space become undefined. +*/ +size_t destroy_mspace(mspace msp); + +/* + create_mspace_with_base uses the memory supplied as the initial base + of a new mspace. Part (less than 128*sizeof(size_t) bytes) of this + space is used for bookkeeping, so the capacity must be at least this + large. (Otherwise 0 is returned.) When this initial space is + exhausted, additional memory will be obtained from the system. + Destroying this space will deallocate all additionally allocated + space (if possible) but not the initial base. +*/ +mspace create_mspace_with_base(void* base, size_t capacity, int locked); + +/* + mspace_mmap_large_chunks controls whether requests for large chunks + are allocated in their own mmapped regions, separate from others in + this mspace. By default this is enabled, which reduces + fragmentation. However, such chunks are not necessarily released to + the system upon destroy_mspace. Disabling by setting to false may + increase fragmentation, but avoids leakage when relying on + destroy_mspace to release all memory allocated using this space. +*/ +int mspace_mmap_large_chunks(mspace msp, int enable); + + +/* + mspace_malloc behaves as malloc, but operates within + the given space. +*/ +void* mspace_malloc(mspace msp, size_t bytes); + +/* + mspace_free behaves as free, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_free is not actually needed. + free may be called instead of mspace_free because freed chunks from + any space are handled by their originating spaces. +*/ +void mspace_free(mspace msp, void* mem); + +/* + mspace_realloc behaves as realloc, but operates within + the given space. + + If compiled with FOOTERS==1, mspace_realloc is not actually + needed. realloc may be called instead of mspace_realloc because + realloced chunks from any space are handled by their originating + spaces. +*/ +void* mspace_realloc(mspace msp, void* mem, size_t newsize); + +/* + mspace_calloc behaves as calloc, but operates within + the given space. +*/ +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size); + +/* + mspace_memalign behaves as memalign, but operates within + the given space. +*/ +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes); + +/* + mspace_independent_calloc behaves as independent_calloc, but + operates within the given space. +*/ +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]); + +/* + mspace_independent_comalloc behaves as independent_comalloc, but + operates within the given space. +*/ +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]); + +/* + mspace_footprint() returns the number of bytes obtained from the + system for this space. +*/ +size_t mspace_footprint(mspace msp); + +/* + mspace_max_footprint() returns the peak number of bytes obtained from the + system for this space. +*/ +size_t mspace_max_footprint(mspace msp); + + +#if !NO_MALLINFO +/* + mspace_mallinfo behaves as mallinfo, but reports properties of + the given space. +*/ +struct mallinfo mspace_mallinfo(mspace msp); +#endif /* NO_MALLINFO */ + +/* + malloc_usable_size(void* p) behaves the same as malloc_usable_size; +*/ + size_t mspace_usable_size(void* mem); + +/* + mspace_malloc_stats behaves as malloc_stats, but reports + properties of the given space. +*/ +void mspace_malloc_stats(mspace msp); + +/* + mspace_trim behaves as malloc_trim, but + operates within the given space. +*/ +int mspace_trim(mspace msp, size_t pad); + +/* + An alias for mallopt. +*/ +int mspace_mallopt(int, int); + +#endif /* MSPACES */ + +#ifdef __cplusplus +}; /* end of extern "C" */ +#endif /* __cplusplus */ + +/* + ======================================================================== + To make a fully customizable malloc.h header file, cut everything + above this line, put into file malloc.h, edit to suit, and #include it + on the next line, as well as in programs that use this malloc. + ======================================================================== +*/ + +/* #include "malloc.h" */ + +/*------------------------------ internal #includes ---------------------- */ + +#ifdef WIN32 +#ifndef __GNUC__ +#pragma warning( disable : 4146 ) /* no "unsigned" warnings */ +#endif +#endif /* WIN32 */ + +#include <stdio.h> /* for printing in malloc_stats */ + +#ifndef LACKS_ERRNO_H +#include <errno.h> /* for MALLOC_FAILURE_ACTION */ +#endif /* LACKS_ERRNO_H */ +#if FOOTERS +#include <time.h> /* for magic initialization */ +#endif /* FOOTERS */ +#ifndef LACKS_STDLIB_H +#include <stdlib.h> /* for abort() */ +#endif /* LACKS_STDLIB_H */ +#ifdef DEBUG +#if ABORT_ON_ASSERT_FAILURE +#define assert(x) if(!(x)) ABORT +#else /* ABORT_ON_ASSERT_FAILURE */ +#include <assert.h> +#endif /* ABORT_ON_ASSERT_FAILURE */ +#else /* DEBUG */ +#ifndef assert +#define assert(x) +#endif +#define DEBUG 0 +#endif /* DEBUG */ +#ifndef LACKS_STRING_H +#include <string.h> /* for memset etc */ +#endif /* LACKS_STRING_H */ +#if USE_BUILTIN_FFS +#ifndef LACKS_STRINGS_H +#include <strings.h> /* for ffs */ +#endif /* LACKS_STRINGS_H */ +#endif /* USE_BUILTIN_FFS */ +#if HAVE_MMAP +#ifndef LACKS_SYS_MMAN_H +#include <sys/mman.h> /* for mmap */ +#endif /* LACKS_SYS_MMAN_H */ +#ifndef LACKS_FCNTL_H +#include <fcntl.h> +#endif /* LACKS_FCNTL_H */ +#endif /* HAVE_MMAP */ +#ifndef LACKS_UNISTD_H +#include <unistd.h> /* for sbrk, sysconf */ +#else /* LACKS_UNISTD_H */ +#if !defined(__FreeBSD__) && !defined(__OpenBSD__) && !defined(__NetBSD__) +extern void* sbrk(ptrdiff_t); +#endif /* FreeBSD etc */ +#endif /* LACKS_UNISTD_H */ + +/* Declarations for locking */ +#if USE_LOCKS +#ifndef WIN32 +#include <pthread.h> +#if defined (__SVR4) && defined (__sun) /* solaris */ +#include <thread.h> +#endif /* solaris */ +#else +#ifndef _M_AMD64 +/* These are already defined on AMD64 builds */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +#ifndef __MINGW32__ +LONG __cdecl _InterlockedCompareExchange(LONG volatile *Dest, LONG Exchange, LONG Comp); +LONG __cdecl _InterlockedExchange(LONG volatile *Target, LONG Value); +#endif +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _M_AMD64 */ +#ifndef __MINGW32__ +#pragma intrinsic (_InterlockedCompareExchange) +#pragma intrinsic (_InterlockedExchange) +#else + /* --[ start GCC compatibility ]---------------------------------------------- + * Compatibility <intrin_x86.h> header for GCC -- GCC equivalents of intrinsic + * Microsoft Visual C++ functions. Originally developed for the ReactOS + * (<http://www.reactos.org/>) and TinyKrnl (<http://www.tinykrnl.org/>) + * projects. + * + * Copyright (c) 2006 KJK::Hyperion <hackbunny@reactos.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + + /*** Atomic operations ***/ + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) > 40100 + #define _ReadWriteBarrier() __sync_synchronize() + #else + static __inline__ __attribute__((always_inline)) long __sync_lock_test_and_set(volatile long * const Target, const long Value) + { + long res; + __asm__ __volatile__("xchg%z0 %2, %0" : "=g" (*(Target)), "=r" (res) : "1" (Value)); + return res; + } + static void __inline__ __attribute__((always_inline)) _MemoryBarrier(void) + { + __asm__ __volatile__("" : : : "memory"); + } + #define _ReadWriteBarrier() _MemoryBarrier() + #endif + /* BUGBUG: GCC only supports full barriers */ + static __inline__ __attribute__((always_inline)) long _InterlockedExchange(volatile long * const Target, const long Value) + { + /* NOTE: __sync_lock_test_and_set would be an acquire barrier, so we force a full barrier */ + _ReadWriteBarrier(); + return __sync_lock_test_and_set(Target, Value); + } + /* --[ end GCC compatibility ]---------------------------------------------- */ +#endif +#define interlockedcompareexchange _InterlockedCompareExchange +#define interlockedexchange _InterlockedExchange +#endif /* Win32 */ +#endif /* USE_LOCKS */ + +/* Declarations for bit scanning on win32 */ +#if defined(_MSC_VER) && _MSC_VER>=1300 +#ifndef BitScanForward /* Try to avoid pulling in WinNT.h */ +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +unsigned char _BitScanForward(unsigned long *index, unsigned long mask); +unsigned char _BitScanReverse(unsigned long *index, unsigned long mask); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#define BitScanForward _BitScanForward +#define BitScanReverse _BitScanReverse +#pragma intrinsic(_BitScanForward) +#pragma intrinsic(_BitScanReverse) +#endif /* BitScanForward */ +#endif /* defined(_MSC_VER) && _MSC_VER>=1300 */ + +#ifndef WIN32 +#ifndef malloc_getpagesize +# ifdef _SC_PAGESIZE /* some SVR4 systems omit an underscore */ +# ifndef _SC_PAGE_SIZE +# define _SC_PAGE_SIZE _SC_PAGESIZE +# endif +# endif +# ifdef _SC_PAGE_SIZE +# define malloc_getpagesize sysconf(_SC_PAGE_SIZE) +# else +# if defined(BSD) || defined(DGUX) || defined(HAVE_GETPAGESIZE) + extern size_t getpagesize(); +# define malloc_getpagesize getpagesize() +# else +# ifdef WIN32 /* use supplied emulation of getpagesize */ +# define malloc_getpagesize getpagesize() +# else +# ifndef LACKS_SYS_PARAM_H +# include <sys/param.h> +# endif +# ifdef EXEC_PAGESIZE +# define malloc_getpagesize EXEC_PAGESIZE +# else +# ifdef NBPG +# ifndef CLSIZE +# define malloc_getpagesize NBPG +# else +# define malloc_getpagesize (NBPG * CLSIZE) +# endif +# else +# ifdef NBPC +# define malloc_getpagesize NBPC +# else +# ifdef PAGESIZE +# define malloc_getpagesize PAGESIZE +# else /* just guess */ +# define malloc_getpagesize ((size_t)4096U) +# endif +# endif +# endif +# endif +# endif +# endif +# endif +#endif +#endif + + + +/* ------------------- size_t and alignment properties -------------------- */ + +/* The byte and bit size of a size_t */ +#define SIZE_T_SIZE (sizeof(size_t)) +#define SIZE_T_BITSIZE (sizeof(size_t) << 3) + +/* Some constants coerced to size_t */ +/* Annoying but necessary to avoid errors on some platforms */ +#define SIZE_T_ZERO ((size_t)0) +#define SIZE_T_ONE ((size_t)1) +#define SIZE_T_TWO ((size_t)2) +#define SIZE_T_FOUR ((size_t)4) +#define TWO_SIZE_T_SIZES (SIZE_T_SIZE<<1) +#define FOUR_SIZE_T_SIZES (SIZE_T_SIZE<<2) +#define SIX_SIZE_T_SIZES (FOUR_SIZE_T_SIZES+TWO_SIZE_T_SIZES) +#define HALF_MAX_SIZE_T (MAX_SIZE_T / 2U) + +/* The bit mask value corresponding to MALLOC_ALIGNMENT */ +#define CHUNK_ALIGN_MASK (MALLOC_ALIGNMENT - SIZE_T_ONE) + +/* True if address a has acceptable alignment */ +#define is_aligned(A) (((size_t)((A)) & (CHUNK_ALIGN_MASK)) == 0) + +/* the number of bytes to offset an address to align it */ +#define align_offset(A)\ + ((((size_t)(A) & CHUNK_ALIGN_MASK) == 0)? 0 :\ + ((MALLOC_ALIGNMENT - ((size_t)(A) & CHUNK_ALIGN_MASK)) & CHUNK_ALIGN_MASK)) + +/* -------------------------- MMAP preliminaries ------------------------- */ + +/* + If HAVE_MORECORE or HAVE_MMAP are false, we just define calls and + checks to fail so compiler optimizer can delete code rather than + using so many "#if"s. +*/ + + +/* MORECORE and MMAP must return MFAIL on failure */ +#define MFAIL ((void*)(MAX_SIZE_T)) +#define CMFAIL ((char*)(MFAIL)) /* defined for convenience */ + +#if HAVE_MMAP + +#ifndef WIN32 +#define MUNMAP_DEFAULT(a, s) munmap((a), (s)) +#define MMAP_PROT (PROT_READ|PROT_WRITE) +#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON) +#define MAP_ANONYMOUS MAP_ANON +#endif /* MAP_ANON */ +#ifdef MAP_ANONYMOUS +#define MMAP_FLAGS (MAP_PRIVATE|MAP_ANONYMOUS) +#define MMAP_DEFAULT(s) mmap(0, (s), MMAP_PROT, MMAP_FLAGS, -1, 0) +#else /* MAP_ANONYMOUS */ +/* + Nearly all versions of mmap support MAP_ANONYMOUS, so the following + is unlikely to be needed, but is supplied just in case. +*/ +#define MMAP_FLAGS (MAP_PRIVATE) +static int dev_zero_fd = -1; /* Cached file descriptor for /dev/zero. */ +#define MMAP_DEFAULT(s) ((dev_zero_fd < 0) ? \ + (dev_zero_fd = open("/dev/zero", O_RDWR), \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) : \ + mmap(0, (s), MMAP_PROT, MMAP_FLAGS, dev_zero_fd, 0)) +#endif /* MAP_ANONYMOUS */ + +#define DIRECT_MMAP_DEFAULT(s) MMAP_DEFAULT(s) + +#else /* WIN32 */ + +/* Win32 MMAP via VirtualAlloc */ +static FORCEINLINE void* win32mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* For direct MMAP, use MEM_TOP_DOWN to minimize interference */ +static FORCEINLINE void* win32direct_mmap(size_t size) { + void* ptr = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_TOP_DOWN, + PAGE_READWRITE); + return (ptr != 0)? ptr: MFAIL; +} + +/* This function supports releasing coalesed segments */ +static FORCEINLINE int win32munmap(void* ptr, size_t size) { + MEMORY_BASIC_INFORMATION minfo; + char* cptr = (char*)ptr; + while (size) { + if (VirtualQuery(cptr, &minfo, sizeof(minfo)) == 0) + return -1; + if (minfo.BaseAddress != cptr || minfo.AllocationBase != cptr || + minfo.State != MEM_COMMIT || minfo.RegionSize > size) + return -1; + if (VirtualFree(cptr, 0, MEM_RELEASE) == 0) + return -1; + cptr += minfo.RegionSize; + size -= minfo.RegionSize; + } + return 0; +} + +#define MMAP_DEFAULT(s) win32mmap(s) +#define MUNMAP_DEFAULT(a, s) win32munmap((a), (s)) +#define DIRECT_MMAP_DEFAULT(s) win32direct_mmap(s) +#endif /* WIN32 */ +#endif /* HAVE_MMAP */ + +#if HAVE_MREMAP +#ifndef WIN32 +#define MREMAP_DEFAULT(addr, osz, nsz, mv) mremap((addr), (osz), (nsz), (mv)) +#endif /* WIN32 */ +#endif /* HAVE_MREMAP */ + + +/** + * Define CALL_MORECORE + */ +#if HAVE_MORECORE + #ifdef MORECORE + #define CALL_MORECORE(S) MORECORE(S) + #else /* MORECORE */ + #define CALL_MORECORE(S) MORECORE_DEFAULT(S) + #endif /* MORECORE */ +#else /* HAVE_MORECORE */ + #define CALL_MORECORE(S) MFAIL +#endif /* HAVE_MORECORE */ + +/** + * Define CALL_MMAP/CALL_MUNMAP/CALL_DIRECT_MMAP + */ +#if HAVE_MMAP + #define IS_MMAPPED_BIT (SIZE_T_ONE) + #define USE_MMAP_BIT (SIZE_T_ONE) + + #ifdef MMAP + #define CALL_MMAP(s) MMAP(s) + #else /* MMAP */ + #define CALL_MMAP(s) MMAP_DEFAULT(s) + #endif /* MMAP */ + #ifdef MUNMAP + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) + #else /* MUNMAP */ + #define CALL_MUNMAP(a, s) MUNMAP_DEFAULT((a), (s)) + #endif /* MUNMAP */ + #ifdef DIRECT_MMAP + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #else /* DIRECT_MMAP */ + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP_DEFAULT(s) + #endif /* DIRECT_MMAP */ +#else /* HAVE_MMAP */ + #define IS_MMAPPED_BIT (SIZE_T_ZERO) + #define USE_MMAP_BIT (SIZE_T_ZERO) + + #define MMAP(s) MFAIL + #define MUNMAP(a, s) (-1) + #define DIRECT_MMAP(s) MFAIL + #define CALL_DIRECT_MMAP(s) DIRECT_MMAP(s) + #define CALL_MMAP(s) MMAP(s) + #define CALL_MUNMAP(a, s) MUNMAP((a), (s)) +#endif /* HAVE_MMAP */ + +/** + * Define CALL_MREMAP + */ +#if HAVE_MMAP && HAVE_MREMAP + #ifdef MREMAP + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP((addr), (osz), (nsz), (mv)) + #else /* MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MREMAP_DEFAULT((addr), (osz), (nsz), (mv)) + #endif /* MREMAP */ +#else /* HAVE_MMAP && HAVE_MREMAP */ + #define CALL_MREMAP(addr, osz, nsz, mv) MFAIL +#endif /* HAVE_MMAP && HAVE_MREMAP */ + +/* mstate bit set if continguous morecore disabled or failed */ +#define USE_NONCONTIGUOUS_BIT (4U) + +/* segment bit set in create_mspace_with_base */ +#define EXTERN_BIT (8U) + + +/* --------------------------- Lock preliminaries ------------------------ */ + +/* + When locks are defined, there is one global lock, plus + one per-mspace lock. + + The global lock_ensures that mparams.magic and other unique + mparams values are initialized only once. It also protects + sequences of calls to MORECORE. In many cases sys_alloc requires + two calls, that should not be interleaved with calls by other + threads. This does not protect against direct calls to MORECORE + by other threads not using this lock, so there is still code to + cope the best we can on interference. + + Per-mspace locks surround calls to malloc, free, etc. To enable use + in layered extensions, per-mspace locks are reentrant. + + Because lock-protected regions generally have bounded times, it is + OK to use the supplied simple spinlocks in the custom versions for + x86. + + If USE_LOCKS is > 1, the definitions of lock routines here are + bypassed, in which case you will need to define at least + INITIAL_LOCK, ACQUIRE_LOCK, RELEASE_LOCK and possibly TRY_LOCK + (which is not used in this malloc, but commonly needed in + extensions.) +*/ + +#if USE_LOCKS == 1 + +#if USE_SPIN_LOCKS +#ifndef WIN32 + +/* Custom pthread-style spin locks on x86 and x64 for gcc */ +struct pthread_mlock_t { + volatile unsigned int l; + volatile unsigned int c; + volatile pthread_t threadid; +}; +#define MLOCK_T struct pthread_mlock_t +#define CURRENT_THREAD pthread_self() +#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), 0) +#define ACQUIRE_LOCK(sl) pthread_acquire_lock(sl) +#define RELEASE_LOCK(sl) pthread_release_lock(sl) +#define TRY_LOCK(sl) pthread_try_lock(sl) +#define SPINS_PER_YIELD 63 + +static MLOCK_T malloc_global_mutex = { 0, 0, 0}; + +static FORCEINLINE int pthread_acquire_lock (MLOCK_T *sl) { + int spins = 0; + volatile unsigned int* lp = &sl->l; + for (;;) { + if (*lp != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 0; + } + } + else { + /* place args to cmpxchgl in locals to evade oddities in some gccs */ + int cmp = 0; + int val = 1; + int ret; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(lp)), "0"(cmp) + : "memory", "cc"); + if (!ret) { + assert(!sl->threadid); + sl->c = 1; + sl->threadid = CURRENT_THREAD; + return 0; + } + if ((++spins & SPINS_PER_YIELD) == 0) { +#if defined (__SVR4) && defined (__sun) /* solaris */ + thr_yield(); +#else +#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__) + sched_yield(); +#else /* no-op yield on unknown systems */ + ; +#endif /* __linux__ || __FreeBSD__ || __APPLE__ */ +#endif /* solaris */ + } + } + } +} + +static FORCEINLINE void pthread_release_lock (MLOCK_T *sl) { + assert(sl->l != 0); + assert(sl->threadid == CURRENT_THREAD); + if (--sl->c == 0) { + sl->threadid = 0; + volatile unsigned int* lp = &sl->l; + int prev = 0; + int ret; + __asm__ __volatile__ ("lock; xchgl %0, %1" + : "=r" (ret) + : "m" (*(lp)), "0"(prev) + : "memory"); + } +} + +static FORCEINLINE int pthread_try_lock (MLOCK_T *sl) { + volatile unsigned int* lp = &sl->l; + if (*lp != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 1; + } + } + else { + int cmp = 0; + int val = 1; + int ret; + __asm__ __volatile__ ("lock; cmpxchgl %1, %2" + : "=a" (ret) + : "r" (val), "m" (*(lp)), "0"(cmp) + : "memory", "cc"); + if (!ret) { + assert(!sl->threadid); + sl->c = 1; + sl->threadid = CURRENT_THREAD; + return 1; + } + } + return 0; +} + + +#else /* WIN32 */ +/* Custom win32-style spin locks on x86 and x64 for MSC */ +struct win32_mlock_t +{ + volatile long l; + volatile unsigned int c; + volatile long threadid; +}; + +#define MLOCK_T struct win32_mlock_t +#define CURRENT_THREAD win32_getcurrentthreadid() +#define INITIAL_LOCK(sl) (memset(sl, 0, sizeof(MLOCK_T)), 0) +#define ACQUIRE_LOCK(sl) win32_acquire_lock(sl) +#define RELEASE_LOCK(sl) win32_release_lock(sl) +#define TRY_LOCK(sl) win32_try_lock(sl) +#define SPINS_PER_YIELD 63 + +static MLOCK_T malloc_global_mutex = { 0, 0, 0}; + +static FORCEINLINE long win32_getcurrentthreadid() { +#ifdef _MSC_VER +#if defined(_M_IX86) + long *threadstruct=(long *)__readfsdword(0x18); + long threadid=threadstruct[0x24/sizeof(long)]; + return threadid; +#elif defined(_M_X64) + /* todo */ + return GetCurrentThreadId(); +#else + return GetCurrentThreadId(); +#endif +#else + return GetCurrentThreadId(); +#endif +} + +static FORCEINLINE int win32_acquire_lock (MLOCK_T *sl) { + int spins = 0; + for (;;) { + if (sl->l != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 0; + } + } + else { + if (!interlockedexchange(&sl->l, 1)) { + assert(!sl->threadid); + sl->c=CURRENT_THREAD; + sl->threadid = CURRENT_THREAD; + sl->c = 1; + return 0; + } + } + if ((++spins & SPINS_PER_YIELD) == 0) + SleepEx(0, FALSE); + } +} + +static FORCEINLINE void win32_release_lock (MLOCK_T *sl) { + assert(sl->threadid == CURRENT_THREAD); + assert(sl->l != 0); + if (--sl->c == 0) { + sl->threadid = 0; + interlockedexchange (&sl->l, 0); + } +} + +static FORCEINLINE int win32_try_lock (MLOCK_T *sl) { + if(sl->l != 0) { + if (sl->threadid == CURRENT_THREAD) { + ++sl->c; + return 1; + } + } + else { + if (!interlockedexchange(&sl->l, 1)){ + assert(!sl->threadid); + sl->threadid = CURRENT_THREAD; + sl->c = 1; + return 1; + } + } + return 0; +} + +#endif /* WIN32 */ +#else /* USE_SPIN_LOCKS */ + +#ifndef WIN32 +/* pthreads-based locks */ + +#define MLOCK_T pthread_mutex_t +#define CURRENT_THREAD pthread_self() +#define INITIAL_LOCK(sl) pthread_init_lock(sl) +#define ACQUIRE_LOCK(sl) pthread_mutex_lock(sl) +#define RELEASE_LOCK(sl) pthread_mutex_unlock(sl) +#define TRY_LOCK(sl) (!pthread_mutex_trylock(sl)) + +static MLOCK_T malloc_global_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* Cope with old-style linux recursive lock initialization by adding */ +/* skipped internal declaration from pthread.h */ +#ifdef linux +#ifndef PTHREAD_MUTEX_RECURSIVE +extern int pthread_mutexattr_setkind_np __P ((pthread_mutexattr_t *__attr, + int __kind)); +#define PTHREAD_MUTEX_RECURSIVE PTHREAD_MUTEX_RECURSIVE_NP +#define pthread_mutexattr_settype(x,y) pthread_mutexattr_setkind_np(x,y) +#endif +#endif + +static int pthread_init_lock (MLOCK_T *sl) { + pthread_mutexattr_t attr; + if (pthread_mutexattr_init(&attr)) return 1; + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE)) return 1; + if (pthread_mutex_init(sl, &attr)) return 1; + if (pthread_mutexattr_destroy(&attr)) return 1; + return 0; +} + +#else /* WIN32 */ +/* Win32 critical sections */ +#define MLOCK_T CRITICAL_SECTION +#define CURRENT_THREAD GetCurrentThreadId() +#define INITIAL_LOCK(s) (!InitializeCriticalSectionAndSpinCount((s), 0x80000000|4000)) +#define ACQUIRE_LOCK(s) (EnterCriticalSection(s), 0) +#define RELEASE_LOCK(s) LeaveCriticalSection(s) +#define TRY_LOCK(s) TryEnterCriticalSection(s) +#define NEED_GLOBAL_LOCK_INIT + +static MLOCK_T malloc_global_mutex; +static volatile long malloc_global_mutex_status; + +/* Use spin loop to initialize global lock */ +static void init_malloc_global_mutex() { + for (;;) { + long stat = malloc_global_mutex_status; + if (stat > 0) + return; + /* transition to < 0 while initializing, then to > 0) */ + if (stat == 0 && + interlockedcompareexchange(&malloc_global_mutex_status, -1, 0) == 0) { + InitializeCriticalSection(&malloc_global_mutex); + interlockedexchange(&malloc_global_mutex_status,1); + return; + } + SleepEx(0, FALSE); + } +} + +#endif /* WIN32 */ +#endif /* USE_SPIN_LOCKS */ +#endif /* USE_LOCKS == 1 */ + +/* ----------------------- User-defined locks ------------------------ */ + +#if USE_LOCKS > 1 +/* Define your own lock implementation here */ +/* #define INITIAL_LOCK(sl) ... */ +/* #define ACQUIRE_LOCK(sl) ... */ +/* #define RELEASE_LOCK(sl) ... */ +/* #define TRY_LOCK(sl) ... */ +/* static MLOCK_T malloc_global_mutex = ... */ +#endif /* USE_LOCKS > 1 */ + +/* ----------------------- Lock-based state ------------------------ */ + +#if USE_LOCKS +#define USE_LOCK_BIT (2U) +#else /* USE_LOCKS */ +#define USE_LOCK_BIT (0U) +#define INITIAL_LOCK(l) +#endif /* USE_LOCKS */ + +#if USE_LOCKS +#define ACQUIRE_MALLOC_GLOBAL_LOCK() ACQUIRE_LOCK(&malloc_global_mutex); +#define RELEASE_MALLOC_GLOBAL_LOCK() RELEASE_LOCK(&malloc_global_mutex); +#else /* USE_LOCKS */ +#define ACQUIRE_MALLOC_GLOBAL_LOCK() +#define RELEASE_MALLOC_GLOBAL_LOCK() +#endif /* USE_LOCKS */ + + +/* ----------------------- Chunk representations ------------------------ */ + +/* + (The following includes lightly edited explanations by Colin Plumb.) + + The malloc_chunk declaration below is misleading (but accurate and + necessary). It declares a "view" into memory allowing access to + necessary fields at known offsets from a given base. + + Chunks of memory are maintained using a `boundary tag' method as + originally described by Knuth. (See the paper by Paul Wilson + ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a survey of such + techniques.) Sizes of free chunks are stored both in the front of + each chunk and at the end. This makes consolidating fragmented + chunks into bigger chunks fast. The head fields also hold bits + representing whether chunks are free or in use. + + Here are some pictures to make it clearer. They are "exploded" to + show that the state of a chunk can be thought of as extending from + the high 31 bits of the head field of its header through the + prev_foot and PINUSE_BIT bit of the following chunk header. + + A chunk that's in use looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk (if P = 0) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 1| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + +- -+ + | | + +- -+ + | : + +- size - sizeof(size_t) available payload bytes -+ + : | + chunk-> +- -+ + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| + | Size of next chunk (may or may not be in use) | +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + And if it's free, it looks like this: + + chunk-> +- -+ + | User payload (must be in use, or we would have merged!) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |P| + | Size of this chunk 0| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Next pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Prev pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- size - sizeof(struct chunk) unused bytes -+ + : | + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |0| + | Size of next chunk (must be in use, or we would have merged)| +-+ + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + +- User payload -+ + : | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0| + +-+ + Note that since we always merge adjacent free chunks, the chunks + adjacent to a free chunk must be in use. + + Given a pointer to a chunk (which can be derived trivially from the + payload pointer) we can, in O(1) time, find out whether the adjacent + chunks are free, and if so, unlink them from the lists that they + are on and merge them with the current chunk. + + Chunks always begin on even word boundaries, so the mem portion + (which is returned to the user) is also on an even word boundary, and + thus at least double-word aligned. + + The P (PINUSE_BIT) bit, stored in the unused low-order bit of the + chunk size (which is always a multiple of two words), is an in-use + bit for the *previous* chunk. If that bit is *clear*, then the + word before the current chunk size contains the previous chunk + size, and can be used to find the front of the previous chunk. + The very first chunk allocated always has this bit set, preventing + access to non-existent (or non-owned) memory. If pinuse is set for + any given chunk, then you CANNOT determine the size of the + previous chunk, and might even get a memory addressing fault when + trying to do so. + + The C (CINUSE_BIT) bit, stored in the unused second-lowest bit of + the chunk size redundantly records whether the current chunk is + inuse. This redundancy enables usage checks within free and realloc, + and reduces indirection when freeing and consolidating chunks. + + Each freshly allocated chunk must have both cinuse and pinuse set. + That is, each allocated chunk borders either a previously allocated + and still in-use chunk, or the base of its memory arena. This is + ensured by making all allocations from the the `lowest' part of any + found chunk. Further, no free chunk physically borders another one, + so each free chunk is known to be preceded and followed by either + inuse chunks or the ends of memory. + + Note that the `foot' of the current chunk is actually represented + as the prev_foot of the NEXT chunk. This makes it easier to + deal with alignments etc but can be very confusing when trying + to extend or adapt this code. + + The exceptions to all this are + + 1. The special chunk `top' is the top-most available chunk (i.e., + the one bordering the end of available memory). It is treated + specially. Top is never included in any bin, is used only if + no other chunk is available, and is released back to the + system if it is very large (see M_TRIM_THRESHOLD). In effect, + the top chunk is treated as larger (and thus less well + fitting) than any other available chunk. The top chunk + doesn't update its trailing size field since there is no next + contiguous chunk that would have to index off it. However, + space is still allocated for it (TOP_FOOT_SIZE) to enable + separation or merging when space is extended. + + 3. Chunks allocated via mmap, which have the lowest-order bit + (IS_MMAPPED_BIT) set in their prev_foot fields, and do not set + PINUSE_BIT in their head fields. Because they are allocated + one-by-one, each must carry its own prev_foot field, which is + also used to hold the offset this chunk has within its mmapped + region, which is needed to preserve alignment. Each mmapped + chunk is trailed by the first two fields of a fake next-chunk + for sake of usage checks. + +*/ + +struct malloc_chunk { + size_t prev_foot; /* Size of previous chunk (if free). */ + size_t head; /* Size and inuse bits. */ + struct malloc_chunk* fd; /* double links -- used only if free. */ + struct malloc_chunk* bk; +}; + +typedef struct malloc_chunk mchunk; +typedef struct malloc_chunk* mchunkptr; +typedef struct malloc_chunk* sbinptr; /* The type of bins of chunks */ +typedef unsigned int bindex_t; /* Described below */ +typedef unsigned int binmap_t; /* Described below */ +typedef unsigned int flag_t; /* The type of various bit flag sets */ + +/* ------------------- Chunks sizes and alignments ----------------------- */ + +#define MCHUNK_SIZE (sizeof(mchunk)) + +#if FOOTERS +#define CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +#else /* FOOTERS */ +#define CHUNK_OVERHEAD (SIZE_T_SIZE) +#endif /* FOOTERS */ + +/* MMapped chunks need a second word of overhead ... */ +#define MMAP_CHUNK_OVERHEAD (TWO_SIZE_T_SIZES) +/* ... and additional padding for fake next-chunk at foot */ +#define MMAP_FOOT_PAD (FOUR_SIZE_T_SIZES) + +/* The smallest size we can malloc is an aligned minimal chunk */ +#define MIN_CHUNK_SIZE\ + ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* conversion from malloc headers to user pointers, and back */ +#define chunk2mem(p) ((void*)((char*)(p) + TWO_SIZE_T_SIZES)) +#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - TWO_SIZE_T_SIZES)) +/* chunk associated with aligned address A */ +#define align_as_chunk(A) (mchunkptr)((A) + align_offset(chunk2mem(A))) + +/* Bounds on request (not chunk) sizes. */ +#define MAX_REQUEST ((-MIN_CHUNK_SIZE) << 2) +#define MIN_REQUEST (MIN_CHUNK_SIZE - CHUNK_OVERHEAD - SIZE_T_ONE) + +/* pad request bytes into a usable size */ +#define pad_request(req) \ + (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK) + +/* pad request, checking for minimum (but not maximum) */ +#define request2size(req) \ + (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req)) + + +/* ------------------ Operations on head and foot fields ----------------- */ + +/* + The head field of a chunk is or'ed with PINUSE_BIT when previous + adjacent chunk in use, and or'ed with CINUSE_BIT if this chunk is in + use. If the chunk was obtained with mmap, the prev_foot field has + IS_MMAPPED_BIT set, otherwise holding the offset of the base of the + mmapped region to the base of the chunk. + + FLAG4_BIT is not used by this malloc, but might be useful in extensions. +*/ + +#define PINUSE_BIT (SIZE_T_ONE) +#define CINUSE_BIT (SIZE_T_TWO) +#define FLAG4_BIT (SIZE_T_FOUR) +#define INUSE_BITS (PINUSE_BIT|CINUSE_BIT) +#define FLAG_BITS (PINUSE_BIT|CINUSE_BIT|FLAG4_BIT) + +/* Head value for fenceposts */ +#define FENCEPOST_HEAD (INUSE_BITS|SIZE_T_SIZE) + +/* extraction of fields from head words */ +#define cinuse(p) ((p)->head & CINUSE_BIT) +#define pinuse(p) ((p)->head & PINUSE_BIT) +#define chunksize(p) ((p)->head & ~(FLAG_BITS)) + +#define clear_pinuse(p) ((p)->head &= ~PINUSE_BIT) +#define clear_cinuse(p) ((p)->head &= ~CINUSE_BIT) + +/* Treat space at ptr +/- offset as a chunk */ +#define chunk_plus_offset(p, s) ((mchunkptr)(((char*)(p)) + (s))) +#define chunk_minus_offset(p, s) ((mchunkptr)(((char*)(p)) - (s))) + +/* Ptr to next or previous physical malloc_chunk. */ +#define next_chunk(p) ((mchunkptr)( ((char*)(p)) + ((p)->head & ~FLAG_BITS))) +#define prev_chunk(p) ((mchunkptr)( ((char*)(p)) - ((p)->prev_foot) )) + +/* extract next chunk's pinuse bit */ +#define next_pinuse(p) ((next_chunk(p)->head) & PINUSE_BIT) + +/* Get/set size at footer */ +#define get_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot) +#define set_foot(p, s) (((mchunkptr)((char*)(p) + (s)))->prev_foot = (s)) + +/* Set size, pinuse bit, and foot */ +#define set_size_and_pinuse_of_free_chunk(p, s)\ + ((p)->head = (s|PINUSE_BIT), set_foot(p, s)) + +/* Set size, pinuse bit, foot, and clear next pinuse */ +#define set_free_with_pinuse(p, s, n)\ + (clear_pinuse(n), set_size_and_pinuse_of_free_chunk(p, s)) + +#define is_mmapped(p)\ + (!((p)->head & PINUSE_BIT) && ((p)->prev_foot & IS_MMAPPED_BIT)) + +/* Get the internal overhead associated with chunk p */ +#define overhead_for(p)\ + (is_mmapped(p)? MMAP_CHUNK_OVERHEAD : CHUNK_OVERHEAD) + +/* Return true if malloced space is not necessarily cleared */ +#if MMAP_CLEARS +#define calloc_must_clear(p) (!is_mmapped(p)) +#else /* MMAP_CLEARS */ +#define calloc_must_clear(p) (1) +#endif /* MMAP_CLEARS */ + +/* ---------------------- Overlaid data structures ----------------------- */ + +/* + When chunks are not in use, they are treated as nodes of either + lists or trees. + + "Small" chunks are stored in circular doubly-linked lists, and look + like this: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk in list | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space (may be 0 bytes long) . + . . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Larger chunks are kept in a form of bitwise digital trees (aka + tries) keyed on chunksizes. Because malloc_tree_chunks are only for + free chunks greater than 256 bytes, their size doesn't impose any + constraints on user chunk sizes. Each node looks like: + + chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Size of previous chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `head:' | Size of chunk, in bytes |P| + mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Forward pointer to next chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Back pointer to previous chunk of same size | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to left child (child[0]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to right child (child[1]) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Pointer to parent | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | bin index of this chunk | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Unused space . + . | +nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + `foot:' | Size of chunk, in bytes | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Each tree holding treenodes is a tree of unique chunk sizes. Chunks + of the same size are arranged in a circularly-linked list, with only + the oldest chunk (the next to be used, in our FIFO ordering) + actually in the tree. (Tree members are distinguished by a non-null + parent pointer.) If a chunk with the same size an an existing node + is inserted, it is linked off the existing node using pointers that + work in the same way as fd/bk pointers of small chunks. + + Each tree contains a power of 2 sized range of chunk sizes (the + smallest is 0x100 <= x < 0x180), which is is divided in half at each + tree level, with the chunks in the smaller half of the range (0x100 + <= x < 0x140 for the top nose) in the left subtree and the larger + half (0x140 <= x < 0x180) in the right subtree. This is, of course, + done by inspecting individual bits. + + Using these rules, each node's left subtree contains all smaller + sizes than its right subtree. However, the node at the root of each + subtree has no particular ordering relationship to either. (The + dividing line between the subtree sizes is based on trie relation.) + If we remove the last chunk of a given size from the interior of the + tree, we need to replace it with a leaf node. The tree ordering + rules permit a node to be replaced by any leaf below it. + + The smallest chunk in a tree (a common operation in a best-fit + allocator) can be found by walking a path to the leftmost leaf in + the tree. Unlike a usual binary tree, where we follow left child + pointers until we reach a null, here we follow the right child + pointer any time the left one is null, until we reach a leaf with + both child pointers null. The smallest chunk in the tree will be + somewhere along that path. + + The worst case number of steps to add, find, or remove a node is + bounded by the number of bits differentiating chunks within + bins. Under current bin calculations, this ranges from 6 up to 21 + (for 32 bit sizes) or up to 53 (for 64 bit sizes). The typical case + is of course much better. +*/ + +struct malloc_tree_chunk { + /* The first four fields must be compatible with malloc_chunk */ + size_t prev_foot; + size_t head; + struct malloc_tree_chunk* fd; + struct malloc_tree_chunk* bk; + + struct malloc_tree_chunk* child[2]; + struct malloc_tree_chunk* parent; + bindex_t index; +}; + +typedef struct malloc_tree_chunk tchunk; +typedef struct malloc_tree_chunk* tchunkptr; +typedef struct malloc_tree_chunk* tbinptr; /* The type of bins of trees */ + +/* A little helper macro for trees */ +#define leftmost_child(t) ((t)->child[0] != 0? (t)->child[0] : (t)->child[1]) + +/* ----------------------------- Segments -------------------------------- */ + +/* + Each malloc space may include non-contiguous segments, held in a + list headed by an embedded malloc_segment record representing the + top-most space. Segments also include flags holding properties of + the space. Large chunks that are directly allocated by mmap are not + included in this list. They are instead independently created and + destroyed without otherwise keeping track of them. + + Segment management mainly comes into play for spaces allocated by + MMAP. Any call to MMAP might or might not return memory that is + adjacent to an existing segment. MORECORE normally contiguously + extends the current space, so this space is almost always adjacent, + which is simpler and faster to deal with. (This is why MORECORE is + used preferentially to MMAP when both are available -- see + sys_alloc.) When allocating using MMAP, we don't use any of the + hinting mechanisms (inconsistently) supported in various + implementations of unix mmap, or distinguish reserving from + committing memory. Instead, we just ask for space, and exploit + contiguity when we get it. It is probably possible to do + better than this on some systems, but no general scheme seems + to be significantly better. + + Management entails a simpler variant of the consolidation scheme + used for chunks to reduce fragmentation -- new adjacent memory is + normally prepended or appended to an existing segment. However, + there are limitations compared to chunk consolidation that mostly + reflect the fact that segment processing is relatively infrequent + (occurring only when getting memory from system) and that we + don't expect to have huge numbers of segments: + + * Segments are not indexed, so traversal requires linear scans. (It + would be possible to index these, but is not worth the extra + overhead and complexity for most programs on most platforms.) + * New segments are only appended to old ones when holding top-most + memory; if they cannot be prepended to others, they are held in + different segments. + + Except for the top-most segment of an mstate, each segment record + is kept at the tail of its segment. Segments are added by pushing + segment records onto the list headed by &mstate.seg for the + containing mstate. + + Segment flags control allocation/merge/deallocation policies: + * If EXTERN_BIT set, then we did not allocate this segment, + and so should not try to deallocate or merge with others. + (This currently holds only for the initial segment passed + into create_mspace_with_base.) + * If IS_MMAPPED_BIT set, the segment may be merged with + other surrounding mmapped segments and trimmed/de-allocated + using munmap. + * If neither bit is set, then the segment was obtained using + MORECORE so can be merged with surrounding MORECORE'd segments + and deallocated/trimmed using MORECORE with negative arguments. +*/ + +struct malloc_segment { + char* base; /* base address */ + size_t size; /* allocated size */ + struct malloc_segment* next; /* ptr to next segment */ + flag_t sflags; /* mmap and extern flag */ +}; + +#define is_mmapped_segment(S) ((S)->sflags & IS_MMAPPED_BIT) +#define is_extern_segment(S) ((S)->sflags & EXTERN_BIT) + +typedef struct malloc_segment msegment; +typedef struct malloc_segment* msegmentptr; + +/* ---------------------------- malloc_state ----------------------------- */ + +/* + A malloc_state holds all of the bookkeeping for a space. + The main fields are: + + Top + The topmost chunk of the currently active segment. Its size is + cached in topsize. The actual size of topmost space is + topsize+TOP_FOOT_SIZE, which includes space reserved for adding + fenceposts and segment records if necessary when getting more + space from the system. The size at which to autotrim top is + cached from mparams in trim_check, except that it is disabled if + an autotrim fails. + + Designated victim (dv) + This is the preferred chunk for servicing small requests that + don't have exact fits. It is normally the chunk split off most + recently to service another small request. Its size is cached in + dvsize. The link fields of this chunk are not maintained since it + is not kept in a bin. + + SmallBins + An array of bin headers for free chunks. These bins hold chunks + with sizes less than MIN_LARGE_SIZE bytes. Each bin contains + chunks of all the same size, spaced 8 bytes apart. To simplify + use in double-linked lists, each bin header acts as a malloc_chunk + pointing to the real first node, if it exists (else pointing to + itself). This avoids special-casing for headers. But to avoid + waste, we allocate only the fd/bk pointers of bins, and then use + repositioning tricks to treat these as the fields of a chunk. + + TreeBins + Treebins are pointers to the roots of trees holding a range of + sizes. There are 2 equally spaced treebins for each power of two + from TREE_SHIFT to TREE_SHIFT+16. The last bin holds anything + larger. + + Bin maps + There is one bit map for small bins ("smallmap") and one for + treebins ("treemap). Each bin sets its bit when non-empty, and + clears the bit when empty. Bit operations are then used to avoid + bin-by-bin searching -- nearly all "search" is done without ever + looking at bins that won't be selected. The bit maps + conservatively use 32 bits per map word, even if on 64bit system. + For a good description of some of the bit-based techniques used + here, see Henry S. Warren Jr's book "Hacker's Delight" (and + supplement at http://hackersdelight.org/). Many of these are + intended to reduce the branchiness of paths through malloc etc, as + well as to reduce the number of memory locations read or written. + + Segments + A list of segments headed by an embedded malloc_segment record + representing the initial space. + + Address check support + The least_addr field is the least address ever obtained from + MORECORE or MMAP. Attempted frees and reallocs of any address less + than this are trapped (unless INSECURE is defined). + + Magic tag + A cross-check field that should always hold same value as mparams.magic. + + Flags + Bits recording whether to use MMAP, locks, or contiguous MORECORE + + Statistics + Each space keeps track of current and maximum system memory + obtained via MORECORE or MMAP. + + Trim support + Fields holding the amount of unused topmost memory that should trigger + timming, and a counter to force periodic scanning to release unused + non-topmost segments. + + Locking + If USE_LOCKS is defined, the "mutex" lock is acquired and released + around every public call using this mspace. + + Extension support + A void* pointer and a size_t field that can be used to help implement + extensions to this malloc. +*/ + +/* Bin types, widths and sizes */ +#define NSMALLBINS (32U) +#define NTREEBINS (32U) +#define SMALLBIN_SHIFT (3U) +#define SMALLBIN_WIDTH (SIZE_T_ONE << SMALLBIN_SHIFT) +#define TREEBIN_SHIFT (8U) +#define MIN_LARGE_SIZE (SIZE_T_ONE << TREEBIN_SHIFT) +#define MAX_SMALL_SIZE (MIN_LARGE_SIZE - SIZE_T_ONE) +#define MAX_SMALL_REQUEST (MAX_SMALL_SIZE - CHUNK_ALIGN_MASK - CHUNK_OVERHEAD) + +struct malloc_state { + binmap_t smallmap; + binmap_t treemap; + size_t dvsize; + size_t topsize; + char* least_addr; + mchunkptr dv; + mchunkptr top; + size_t trim_check; + size_t release_checks; + size_t magic; + mchunkptr smallbins[(NSMALLBINS+1)*2]; + tbinptr treebins[NTREEBINS]; + size_t footprint; + size_t max_footprint; + flag_t mflags; +#if USE_LOCKS + MLOCK_T mutex; /* locate lock among fields that rarely change */ +#endif /* USE_LOCKS */ + msegment seg; + void* extp; /* Unused but available for extensions */ + size_t exts; +}; + +typedef struct malloc_state* mstate; + +/* ------------- Global malloc_state and malloc_params ------------------- */ + +/* + malloc_params holds global properties, including those that can be + dynamically set using mallopt. There is a single instance, mparams, + initialized in init_mparams. Note that the non-zeroness of "magic" + also serves as an initialization flag. +*/ + +struct malloc_params { + volatile size_t magic; + size_t page_size; + size_t granularity; + size_t mmap_threshold; + size_t trim_threshold; + flag_t default_mflags; +}; + +static struct malloc_params mparams; + +/* Ensure mparams initialized */ +#define ensure_initialization() ((void)(mparams.magic != 0 || init_mparams())) + +#if !ONLY_MSPACES + +/* The global malloc_state used for all non-"mspace" calls */ +static struct malloc_state _gm_; +#define gm (&_gm_) +#define is_global(M) ((M) == &_gm_) + +#endif /* !ONLY_MSPACES */ + +#define is_initialized(M) ((M)->top != 0) + +/* -------------------------- system alloc setup ------------------------- */ + +/* Operations on mflags */ + +#define use_lock(M) ((M)->mflags & USE_LOCK_BIT) +#define enable_lock(M) ((M)->mflags |= USE_LOCK_BIT) +#define disable_lock(M) ((M)->mflags &= ~USE_LOCK_BIT) + +#define use_mmap(M) ((M)->mflags & USE_MMAP_BIT) +#define enable_mmap(M) ((M)->mflags |= USE_MMAP_BIT) +#define disable_mmap(M) ((M)->mflags &= ~USE_MMAP_BIT) + +#define use_noncontiguous(M) ((M)->mflags & USE_NONCONTIGUOUS_BIT) +#define disable_contiguous(M) ((M)->mflags |= USE_NONCONTIGUOUS_BIT) + +#define set_lock(M,L)\ + ((M)->mflags = (L)?\ + ((M)->mflags | USE_LOCK_BIT) :\ + ((M)->mflags & ~USE_LOCK_BIT)) + +/* page-align a size */ +#define page_align(S)\ + (((S) + (mparams.page_size - SIZE_T_ONE)) & ~(mparams.page_size - SIZE_T_ONE)) + +/* granularity-align a size */ +#define granularity_align(S)\ + (((S) + (mparams.granularity - SIZE_T_ONE))\ + & ~(mparams.granularity - SIZE_T_ONE)) + + +/* For mmap, use granularity alignment on windows, else page-align */ +#ifdef WIN32 +#define mmap_align(S) granularity_align(S) +#else +#define mmap_align(S) page_align(S) +#endif + +/* For sys_alloc, enough padding to ensure can malloc request on success */ +#define SYS_ALLOC_PADDING (TOP_FOOT_SIZE + MALLOC_ALIGNMENT) + +#define is_page_aligned(S)\ + (((size_t)(S) & (mparams.page_size - SIZE_T_ONE)) == 0) +#define is_granularity_aligned(S)\ + (((size_t)(S) & (mparams.granularity - SIZE_T_ONE)) == 0) + +/* True if segment S holds address A */ +#define segment_holds(S, A)\ + ((char*)(A) >= S->base && (char*)(A) < S->base + S->size) + +/* Return segment holding given address */ +static msegmentptr segment_holding(mstate m, char* addr) { + msegmentptr sp = &m->seg; + for (;;) { + if (addr >= sp->base && addr < sp->base + sp->size) + return sp; + if ((sp = sp->next) == 0) + return 0; + } +} + +/* Return true if segment contains a segment link */ +static int has_segment_link(mstate m, msegmentptr ss) { + msegmentptr sp = &m->seg; + for (;;) { + if ((char*)sp >= ss->base && (char*)sp < ss->base + ss->size) + return 1; + if ((sp = sp->next) == 0) + return 0; + } +} + +#ifndef MORECORE_CANNOT_TRIM +#define should_trim(M,s) ((s) > (M)->trim_check) +#else /* MORECORE_CANNOT_TRIM */ +#define should_trim(M,s) (0) +#endif /* MORECORE_CANNOT_TRIM */ + +/* + TOP_FOOT_SIZE is padding at the end of a segment, including space + that may be needed to place segment records and fenceposts when new + noncontiguous segments are added. +*/ +#define TOP_FOOT_SIZE\ + (align_offset(chunk2mem(0))+pad_request(sizeof(struct malloc_segment))+MIN_CHUNK_SIZE) + + +/* ------------------------------- Hooks -------------------------------- */ + +/* + PREACTION should be defined to return 0 on success, and nonzero on + failure. If you are not using locking, you can redefine these to do + anything you like. +*/ + +#if USE_LOCKS + +#define PREACTION(M) ((use_lock(M))? ACQUIRE_LOCK(&(M)->mutex) : 0) +#define POSTACTION(M) { if (use_lock(M)) RELEASE_LOCK(&(M)->mutex); } +#else /* USE_LOCKS */ + +#ifndef PREACTION +#define PREACTION(M) (0) +#endif /* PREACTION */ + +#ifndef POSTACTION +#define POSTACTION(M) +#endif /* POSTACTION */ + +#endif /* USE_LOCKS */ + +/* + CORRUPTION_ERROR_ACTION is triggered upon detected bad addresses. + USAGE_ERROR_ACTION is triggered on detected bad frees and + reallocs. The argument p is an address that might have triggered the + fault. It is ignored by the two predefined actions, but might be + useful in custom actions that try to help diagnose errors. +*/ + +#if PROCEED_ON_ERROR + +/* A count of the number of corruption errors causing resets */ +int malloc_corruption_error_count; + +/* default corruption action */ +static void reset_on_error(mstate m); + +#define CORRUPTION_ERROR_ACTION(m) reset_on_error(m) +#define USAGE_ERROR_ACTION(m, p) + +#else /* PROCEED_ON_ERROR */ + +#ifndef CORRUPTION_ERROR_ACTION +#define CORRUPTION_ERROR_ACTION(m) ABORT +#endif /* CORRUPTION_ERROR_ACTION */ + +#ifndef USAGE_ERROR_ACTION +#define USAGE_ERROR_ACTION(m,p) ABORT +#endif /* USAGE_ERROR_ACTION */ + +#endif /* PROCEED_ON_ERROR */ + +/* -------------------------- Debugging setup ---------------------------- */ + +#if ! DEBUG + +#define check_free_chunk(M,P) +#define check_inuse_chunk(M,P) +#define check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) +#define check_malloc_state(M) +#define check_top_chunk(M,P) + +#else /* DEBUG */ +#define check_free_chunk(M,P) do_check_free_chunk(M,P) +#define check_inuse_chunk(M,P) do_check_inuse_chunk(M,P) +#define check_top_chunk(M,P) do_check_top_chunk(M,P) +#define check_malloced_chunk(M,P,N) do_check_malloced_chunk(M,P,N) +#define check_mmapped_chunk(M,P) do_check_mmapped_chunk(M,P) +#define check_malloc_state(M) do_check_malloc_state(M) + +static void do_check_any_chunk(mstate m, mchunkptr p); +static void do_check_top_chunk(mstate m, mchunkptr p); +static void do_check_mmapped_chunk(mstate m, mchunkptr p); +static void do_check_inuse_chunk(mstate m, mchunkptr p); +static void do_check_free_chunk(mstate m, mchunkptr p); +static void do_check_malloced_chunk(mstate m, void* mem, size_t s); +static void do_check_tree(mstate m, tchunkptr t); +static void do_check_treebin(mstate m, bindex_t i); +static void do_check_smallbin(mstate m, bindex_t i); +static void do_check_malloc_state(mstate m); +static int bin_find(mstate m, mchunkptr x); +static size_t traverse_and_check(mstate m); +#endif /* DEBUG */ + +/* ---------------------------- Indexing Bins ---------------------------- */ + +#define is_small(s) (((s) >> SMALLBIN_SHIFT) < NSMALLBINS) +#define small_index(s) ((s) >> SMALLBIN_SHIFT) +#define small_index2size(i) ((i) << SMALLBIN_SHIFT) +#define MIN_SMALL_INDEX (small_index(MIN_CHUNK_SIZE)) + +/* addressing by index. See above about smallbin repositioning */ +#define smallbin_at(M, i) ((sbinptr)((char*)&((M)->smallbins[(i)<<1]))) +#define treebin_at(M,i) (&((M)->treebins[i])) + +/* assign tree index for size S to variable I. Use x86 asm if possible */ +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_tree_index(S, I)\ +{\ + unsigned int X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + __asm__("bsrl\t%1, %0\n\t" : "=r" (K) : "rm" (X));\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K = _bit_scan_reverse (X); \ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int K;\ + _BitScanReverse((DWORD *) &K, X);\ + I = (bindex_t)((K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1)));\ + }\ +} + +#else /* GNUC */ +#define compute_tree_index(S, I)\ +{\ + size_t X = S >> TREEBIN_SHIFT;\ + if (X == 0)\ + I = 0;\ + else if (X > 0xFFFF)\ + I = NTREEBINS-1;\ + else {\ + unsigned int Y = (unsigned int)X;\ + unsigned int N = ((Y - 0x100) >> 16) & 8;\ + unsigned int K = (((Y <<= N) - 0x1000) >> 16) & 4;\ + N += K;\ + N += K = (((Y <<= K) - 0x4000) >> 16) & 2;\ + K = 14 - N + ((Y <<= K) >> 15);\ + I = (K << 1) + ((S >> (K + (TREEBIN_SHIFT-1)) & 1));\ + }\ +} +#endif /* GNUC */ + +/* Bit representing maximum resolved size in a treebin at i */ +#define bit_for_tree_index(i) \ + (i == NTREEBINS-1)? (SIZE_T_BITSIZE-1) : (((i) >> 1) + TREEBIN_SHIFT - 2) + +/* Shift placing maximum resolved bit in a treebin at i as sign bit */ +#define leftshift_for_tree_index(i) \ + ((i == NTREEBINS-1)? 0 : \ + ((SIZE_T_BITSIZE-SIZE_T_ONE) - (((i) >> 1) + TREEBIN_SHIFT - 2))) + +/* The size of the smallest chunk held in bin with index i */ +#define minsize_for_tree_index(i) \ + ((SIZE_T_ONE << (((i) >> 1) + TREEBIN_SHIFT)) | \ + (((size_t)((i) & SIZE_T_ONE)) << (((i) >> 1) + TREEBIN_SHIFT - 1))) + + +/* ------------------------ Operations on bin maps ----------------------- */ + +/* bit corresponding to given index */ +#define idx2bit(i) ((binmap_t)(1) << (i)) + +/* Mark/Clear bits with given index */ +#define mark_smallmap(M,i) ((M)->smallmap |= idx2bit(i)) +#define clear_smallmap(M,i) ((M)->smallmap &= ~idx2bit(i)) +#define smallmap_is_marked(M,i) ((M)->smallmap & idx2bit(i)) + +#define mark_treemap(M,i) ((M)->treemap |= idx2bit(i)) +#define clear_treemap(M,i) ((M)->treemap &= ~idx2bit(i)) +#define treemap_is_marked(M,i) ((M)->treemap & idx2bit(i)) + +/* isolate the least set bit of a bitmap */ +#define least_bit(x) ((x) & -(x)) + +/* mask with all bits to left of least bit of x on */ +#define left_bits(x) ((x<<1) | -(x<<1)) + +/* mask with all bits to left of or equal to least bit of x on */ +#define same_or_left_bits(x) ((x) | -(x)) + +/* index corresponding to given bit. Use x86 asm if possible */ + +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + __asm__("bsfl\t%1, %0\n\t" : "=r" (J) : "rm" (X));\ + I = (bindex_t)J;\ +} + +#elif defined (__INTEL_COMPILER) +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + J = _bit_scan_forward (X); \ + I = (bindex_t)J;\ +} + +#elif defined(_MSC_VER) && _MSC_VER>=1300 +#define compute_bit2idx(X, I)\ +{\ + unsigned int J;\ + _BitScanForward((DWORD *) &J, X);\ + I = (bindex_t)J;\ +} + +#elif USE_BUILTIN_FFS +#define compute_bit2idx(X, I) I = ffs(X)-1 + +#else +#define compute_bit2idx(X, I)\ +{\ + unsigned int Y = X - 1;\ + unsigned int K = Y >> (16-4) & 16;\ + unsigned int N = K; Y >>= K;\ + N += K = Y >> (8-3) & 8; Y >>= K;\ + N += K = Y >> (4-2) & 4; Y >>= K;\ + N += K = Y >> (2-1) & 2; Y >>= K;\ + N += K = Y >> (1-0) & 1; Y >>= K;\ + I = (bindex_t)(N + Y);\ +} +#endif /* GNUC */ + + +/* ----------------------- Runtime Check Support ------------------------- */ + +/* + For security, the main invariant is that malloc/free/etc never + writes to a static address other than malloc_state, unless static + malloc_state itself has been corrupted, which cannot occur via + malloc (because of these checks). In essence this means that we + believe all pointers, sizes, maps etc held in malloc_state, but + check all of those linked or offsetted from other embedded data + structures. These checks are interspersed with main code in a way + that tends to minimize their run-time cost. + + When FOOTERS is defined, in addition to range checking, we also + verify footer fields of inuse chunks, which can be used guarantee + that the mstate controlling malloc/free is intact. This is a + streamlined version of the approach described by William Robertson + et al in "Run-time Detection of Heap-based Overflows" LISA'03 + http://www.usenix.org/events/lisa03/tech/robertson.html The footer + of an inuse chunk holds the xor of its mstate and a random seed, + that is checked upon calls to free() and realloc(). This is + (probablistically) unguessable from outside the program, but can be + computed by any code successfully malloc'ing any chunk, so does not + itself provide protection against code that has already broken + security through some other means. Unlike Robertson et al, we + always dynamically check addresses of all offset chunks (previous, + next, etc). This turns out to be cheaper than relying on hashes. +*/ + +#if !INSECURE +/* Check if address a is at least as high as any from MORECORE or MMAP */ +#define ok_address(M, a) ((char*)(a) >= (M)->least_addr) +/* Check if address of next chunk n is higher than base chunk p */ +#define ok_next(p, n) ((char*)(p) < (char*)(n)) +/* Check if p has its cinuse bit on */ +#define ok_cinuse(p) cinuse(p) +/* Check if p has its pinuse bit on */ +#define ok_pinuse(p) pinuse(p) + +#else /* !INSECURE */ +#define ok_address(M, a) (1) +#define ok_next(b, n) (1) +#define ok_cinuse(p) (1) +#define ok_pinuse(p) (1) +#endif /* !INSECURE */ + +#if (FOOTERS && !INSECURE) +/* Check if (alleged) mstate m has expected magic field */ +#define ok_magic(M) ((M)->magic == mparams.magic) +#else /* (FOOTERS && !INSECURE) */ +#define ok_magic(M) (1) +#endif /* (FOOTERS && !INSECURE) */ + + +/* In gcc, use __builtin_expect to minimize impact of checks */ +#if !INSECURE +#if defined(__GNUC__) && __GNUC__ >= 3 +#define RTCHECK(e) __builtin_expect(e, 1) +#else /* GNUC */ +#define RTCHECK(e) (e) +#endif /* GNUC */ +#else /* !INSECURE */ +#define RTCHECK(e) (1) +#endif /* !INSECURE */ + +/* macros to set up inuse chunks with or without footers */ + +#if !FOOTERS + +#define mark_inuse_foot(M,p,s) + +/* Set cinuse bit and pinuse bit of next chunk */ +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set cinuse and pinuse of this chunk and pinuse of next chunk */ +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + ((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT) + +/* Set size, cinuse and pinuse bit of this chunk */ +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT)) + +#else /* FOOTERS */ + +/* Set foot of inuse chunk to be xor of mstate and seed */ +#define mark_inuse_foot(M,p,s)\ + (((mchunkptr)((char*)(p) + (s)))->prev_foot = ((size_t)(M) ^ mparams.magic)) + +#define get_mstate_for(p)\ + ((mstate)(((mchunkptr)((char*)(p) +\ + (chunksize(p))))->prev_foot ^ mparams.magic)) + +#define set_inuse(M,p,s)\ + ((p)->head = (((p)->head & PINUSE_BIT)|s|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT), \ + mark_inuse_foot(M,p,s)) + +#define set_inuse_and_pinuse(M,p,s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + (((mchunkptr)(((char*)(p)) + (s)))->head |= PINUSE_BIT),\ + mark_inuse_foot(M,p,s)) + +#define set_size_and_pinuse_of_inuse_chunk(M, p, s)\ + ((p)->head = (s|PINUSE_BIT|CINUSE_BIT),\ + mark_inuse_foot(M, p, s)) + +#endif /* !FOOTERS */ + +/* ---------------------------- setting mparams -------------------------- */ + +/* Initialize mparams */ +static int init_mparams(void) { +#ifdef NEED_GLOBAL_LOCK_INIT + if (malloc_global_mutex_status <= 0) + init_malloc_global_mutex(); +#endif + + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if (mparams.magic == 0) { + size_t magic; + size_t psize; + size_t gsize; + +#ifndef WIN32 + psize = malloc_getpagesize; + gsize = ((DEFAULT_GRANULARITY != 0)? DEFAULT_GRANULARITY : psize); +#else /* WIN32 */ + { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + psize = system_info.dwPageSize; + gsize = ((DEFAULT_GRANULARITY != 0)? + DEFAULT_GRANULARITY : system_info.dwAllocationGranularity); + } +#endif /* WIN32 */ + + /* Sanity-check configuration: + size_t must be unsigned and as wide as pointer type. + ints must be at least 4 bytes. + alignment must be at least 8. + Alignment, min chunk size, and page size must all be powers of 2. + */ + if ((sizeof(size_t) != sizeof(char*)) || + (MAX_SIZE_T < MIN_CHUNK_SIZE) || + (sizeof(int) < 4) || + (MALLOC_ALIGNMENT < (size_t)8U) || + ((MALLOC_ALIGNMENT & (MALLOC_ALIGNMENT-SIZE_T_ONE)) != 0) || + ((MCHUNK_SIZE & (MCHUNK_SIZE-SIZE_T_ONE)) != 0) || + ((gsize & (gsize-SIZE_T_ONE)) != 0) || + ((psize & (psize-SIZE_T_ONE)) != 0)) + ABORT; + + mparams.granularity = gsize; + mparams.page_size = psize; + mparams.mmap_threshold = DEFAULT_MMAP_THRESHOLD; + mparams.trim_threshold = DEFAULT_TRIM_THRESHOLD; +#if MORECORE_CONTIGUOUS + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT; +#else /* MORECORE_CONTIGUOUS */ + mparams.default_mflags = USE_LOCK_BIT|USE_MMAP_BIT|USE_NONCONTIGUOUS_BIT; +#endif /* MORECORE_CONTIGUOUS */ + +#if !ONLY_MSPACES + /* Set up lock for main malloc area */ + gm->mflags = mparams.default_mflags; + INITIAL_LOCK(&gm->mutex); +#endif + +#if (FOOTERS && !INSECURE) + { +#if USE_DEV_RANDOM + int fd; + unsigned char buf[sizeof(size_t)]; + /* Try to use /dev/urandom, else fall back on using time */ + if ((fd = open("/dev/urandom", O_RDONLY)) >= 0 && + read(fd, buf, sizeof(buf)) == sizeof(buf)) { + magic = *((size_t *) buf); + close(fd); + } + else +#endif /* USE_DEV_RANDOM */ +#ifdef WIN32 + magic = (size_t)(GetTickCount() ^ (size_t)0x55555555U); +#else + magic = (size_t)(time(0) ^ (size_t)0x55555555U); +#endif + magic |= (size_t)8U; /* ensure nonzero */ + magic &= ~(size_t)7U; /* improve chances of fault for bad values */ + } +#else /* (FOOTERS && !INSECURE) */ + magic = (size_t)0x58585858U; +#endif /* (FOOTERS && !INSECURE) */ + + mparams.magic = magic; + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +} + +/* support for mallopt */ +static int change_mparam(int param_number, int value) { + size_t val = (value == -1)? MAX_SIZE_T : (size_t)value; + ensure_initialization(); + switch(param_number) { + case M_TRIM_THRESHOLD: + mparams.trim_threshold = val; + return 1; + case M_GRANULARITY: + if (val >= mparams.page_size && ((val & (val-1)) == 0)) { + mparams.granularity = val; + return 1; + } + else + return 0; + case M_MMAP_THRESHOLD: + mparams.mmap_threshold = val; + return 1; + default: + return 0; + } +} + +#if DEBUG +/* ------------------------- Debugging Support --------------------------- */ + +/* Check properties of any chunk, whether free, inuse, mmapped etc */ +static void do_check_any_chunk(mstate m, mchunkptr p) { + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); +} + +/* Check properties of top chunk */ +static void do_check_top_chunk(mstate m, mchunkptr p) { + msegmentptr sp = segment_holding(m, (char*)p); + size_t sz = p->head & ~INUSE_BITS; /* third-lowest bit can be set! */ + assert(sp != 0); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(sz == m->topsize); + assert(sz > 0); + assert(sz == ((sp->base + sp->size) - (char*)p) - TOP_FOOT_SIZE); + assert(pinuse(p)); + assert(!pinuse(chunk_plus_offset(p, sz))); +} + +/* Check properties of (inuse) mmapped chunks */ +static void do_check_mmapped_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + size_t len = (sz + (p->prev_foot & ~IS_MMAPPED_BIT) + MMAP_FOOT_PAD); + assert(is_mmapped(p)); + assert(use_mmap(m)); + assert((is_aligned(chunk2mem(p))) || (p->head == FENCEPOST_HEAD)); + assert(ok_address(m, p)); + assert(!is_small(sz)); + assert((len & (mparams.page_size-SIZE_T_ONE)) == 0); + assert(chunk_plus_offset(p, sz)->head == FENCEPOST_HEAD); + assert(chunk_plus_offset(p, sz+SIZE_T_SIZE)->head == 0); +} + +/* Check properties of inuse chunks */ +static void do_check_inuse_chunk(mstate m, mchunkptr p) { + do_check_any_chunk(m, p); + assert(cinuse(p)); + assert(next_pinuse(p)); + /* If not pinuse and not mmapped, previous chunk has OK offset */ + assert(is_mmapped(p) || pinuse(p) || next_chunk(prev_chunk(p)) == p); + if (is_mmapped(p)) + do_check_mmapped_chunk(m, p); +} + +/* Check properties of free chunks */ +static void do_check_free_chunk(mstate m, mchunkptr p) { + size_t sz = chunksize(p); + mchunkptr next = chunk_plus_offset(p, sz); + do_check_any_chunk(m, p); + assert(!cinuse(p)); + assert(!next_pinuse(p)); + assert (!is_mmapped(p)); + if (p != m->dv && p != m->top) { + if (sz >= MIN_CHUNK_SIZE) { + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(is_aligned(chunk2mem(p))); + assert(next->prev_foot == sz); + assert(pinuse(p)); + assert (next == m->top || cinuse(next)); + assert(p->fd->bk == p); + assert(p->bk->fd == p); + } + else /* markers are always of size SIZE_T_SIZE */ + assert(sz == SIZE_T_SIZE); + } +} + +/* Check properties of malloced chunks at the point they are malloced */ +static void do_check_malloced_chunk(mstate m, void* mem, size_t s) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + size_t sz = p->head & ~(PINUSE_BIT|CINUSE_BIT); + do_check_inuse_chunk(m, p); + assert((sz & CHUNK_ALIGN_MASK) == 0); + assert(sz >= MIN_CHUNK_SIZE); + assert(sz >= s); + /* unless mmapped, size is less than MIN_CHUNK_SIZE more than request */ + assert(is_mmapped(p) || sz < (s + MIN_CHUNK_SIZE)); + } +} + +/* Check a tree and its subtrees. */ +static void do_check_tree(mstate m, tchunkptr t) { + tchunkptr head = 0; + tchunkptr u = t; + bindex_t tindex = t->index; + size_t tsize = chunksize(t); + bindex_t idx; + compute_tree_index(tsize, idx); + assert(tindex == idx); + assert(tsize >= MIN_LARGE_SIZE); + assert(tsize >= minsize_for_tree_index(idx)); + assert((idx == NTREEBINS-1) || (tsize < minsize_for_tree_index((idx+1)))); + + do { /* traverse through chain of same-sized nodes */ + do_check_any_chunk(m, ((mchunkptr)u)); + assert(u->index == tindex); + assert(chunksize(u) == tsize); + assert(!cinuse(u)); + assert(!next_pinuse(u)); + assert(u->fd->bk == u); + assert(u->bk->fd == u); + if (u->parent == 0) { + assert(u->child[0] == 0); + assert(u->child[1] == 0); + } + else { + assert(head == 0); /* only one node on chain has parent */ + head = u; + assert(u->parent != u); + assert (u->parent->child[0] == u || + u->parent->child[1] == u || + *((tbinptr*)(u->parent)) == u); + if (u->child[0] != 0) { + assert(u->child[0]->parent == u); + assert(u->child[0] != u); + do_check_tree(m, u->child[0]); + } + if (u->child[1] != 0) { + assert(u->child[1]->parent == u); + assert(u->child[1] != u); + do_check_tree(m, u->child[1]); + } + if (u->child[0] != 0 && u->child[1] != 0) { + assert(chunksize(u->child[0]) < chunksize(u->child[1])); + } + } + u = u->fd; + } while (u != t); + assert(head != 0); +} + +/* Check all the chunks in a treebin. */ +static void do_check_treebin(mstate m, bindex_t i) { + tbinptr* tb = treebin_at(m, i); + tchunkptr t = *tb; + int empty = (m->treemap & (1U << i)) == 0; + if (t == 0) + assert(empty); + if (!empty) + do_check_tree(m, t); +} + +/* Check all the chunks in a smallbin. */ +static void do_check_smallbin(mstate m, bindex_t i) { + sbinptr b = smallbin_at(m, i); + mchunkptr p = b->bk; + unsigned int empty = (m->smallmap & (1U << i)) == 0; + if (p == b) + assert(empty); + if (!empty) { + for (; p != b; p = p->bk) { + size_t size = chunksize(p); + mchunkptr q; + /* each chunk claims to be free */ + do_check_free_chunk(m, p); + /* chunk belongs in bin */ + assert(small_index(size) == i); + assert(p->bk == b || chunksize(p->bk) == chunksize(p)); + /* chunk is followed by an inuse chunk */ + q = next_chunk(p); + if (q->head != FENCEPOST_HEAD) + do_check_inuse_chunk(m, q); + } + } +} + +/* Find x in a bin. Used in other check functions. */ +static int bin_find(mstate m, mchunkptr x) { + size_t size = chunksize(x); + if (is_small(size)) { + bindex_t sidx = small_index(size); + sbinptr b = smallbin_at(m, sidx); + if (smallmap_is_marked(m, sidx)) { + mchunkptr p = b; + do { + if (p == x) + return 1; + } while ((p = p->fd) != b); + } + } + else { + bindex_t tidx; + compute_tree_index(size, tidx); + if (treemap_is_marked(m, tidx)) { + tchunkptr t = *treebin_at(m, tidx); + size_t sizebits = size << leftshift_for_tree_index(tidx); + while (t != 0 && chunksize(t) != size) { + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + sizebits <<= 1; + } + if (t != 0) { + tchunkptr u = t; + do { + if (u == (tchunkptr)x) + return 1; + } while ((u = u->fd) != t); + } + } + } + return 0; +} + +/* Traverse each chunk and check it; return total */ +static size_t traverse_and_check(mstate m) { + size_t sum = 0; + if (is_initialized(m)) { + msegmentptr s = &m->seg; + sum += m->topsize + TOP_FOOT_SIZE; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + mchunkptr lastq = 0; + assert(pinuse(q)); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + sum += chunksize(q); + if (cinuse(q)) { + assert(!bin_find(m, q)); + do_check_inuse_chunk(m, q); + } + else { + assert(q == m->dv || bin_find(m, q)); + assert(lastq == 0 || cinuse(lastq)); /* Not 2 consecutive free */ + do_check_free_chunk(m, q); + } + lastq = q; + q = next_chunk(q); + } + s = s->next; + } + } + return sum; +} + +/* Check all properties of malloc_state. */ +static void do_check_malloc_state(mstate m) { + bindex_t i; + size_t total; + /* check bins */ + for (i = 0; i < NSMALLBINS; ++i) + do_check_smallbin(m, i); + for (i = 0; i < NTREEBINS; ++i) + do_check_treebin(m, i); + + if (m->dvsize != 0) { /* check dv chunk */ + do_check_any_chunk(m, m->dv); + assert(m->dvsize == chunksize(m->dv)); + assert(m->dvsize >= MIN_CHUNK_SIZE); + assert(bin_find(m, m->dv) == 0); + } + + if (m->top != 0) { /* check top chunk */ + do_check_top_chunk(m, m->top); + /*assert(m->topsize == chunksize(m->top)); redundant */ + assert(m->topsize > 0); + assert(bin_find(m, m->top) == 0); + } + + total = traverse_and_check(m); + assert(total <= m->footprint); + assert(m->footprint <= m->max_footprint); +} +#endif /* DEBUG */ + +/* ----------------------------- statistics ------------------------------ */ + +#if !NO_MALLINFO +static struct mallinfo internal_mallinfo(mstate m) { + struct mallinfo nm = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + ensure_initialization(); + if (!PREACTION(m)) { + check_malloc_state(m); + if (is_initialized(m)) { + size_t nfree = SIZE_T_ONE; /* top always free */ + size_t mfree = m->topsize + TOP_FOOT_SIZE; + size_t sum = mfree; + msegmentptr s = &m->seg; + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + size_t sz = chunksize(q); + sum += sz; + if (!cinuse(q)) { + mfree += sz; + ++nfree; + } + q = next_chunk(q); + } + s = s->next; + } + + nm.arena = sum; + nm.ordblks = nfree; + nm.hblkhd = m->footprint - sum; + nm.usmblks = m->max_footprint; + nm.uordblks = m->footprint - mfree; + nm.fordblks = mfree; + nm.keepcost = m->topsize; + } + + POSTACTION(m); + } + return nm; +} +#endif /* !NO_MALLINFO */ + +static void internal_malloc_stats(mstate m) { + ensure_initialization(); + if (!PREACTION(m)) { + size_t maxfp = 0; + size_t fp = 0; + size_t used = 0; + check_malloc_state(m); + if (is_initialized(m)) { + msegmentptr s = &m->seg; + maxfp = m->max_footprint; + fp = m->footprint; + used = fp - (m->topsize + TOP_FOOT_SIZE); + + while (s != 0) { + mchunkptr q = align_as_chunk(s->base); + while (segment_holds(s, q) && + q != m->top && q->head != FENCEPOST_HEAD) { + if (!cinuse(q)) + used -= chunksize(q); + q = next_chunk(q); + } + s = s->next; + } + } + + fprintf(stderr, "max system bytes = %10lu\n", (unsigned long)(maxfp)); + fprintf(stderr, "system bytes = %10lu\n", (unsigned long)(fp)); + fprintf(stderr, "in use bytes = %10lu\n", (unsigned long)(used)); + + POSTACTION(m); + } +} + +/* ----------------------- Operations on smallbins ----------------------- */ + +/* + Various forms of linking and unlinking are defined as macros. Even + the ones for trees, which are very long but have very short typical + paths. This is ugly but reduces reliance on inlining support of + compilers. +*/ + +/* Link a free chunk into a smallbin */ +#define insert_small_chunk(M, P, S) {\ + bindex_t I = small_index(S);\ + mchunkptr B = smallbin_at(M, I);\ + mchunkptr F = B;\ + assert(S >= MIN_CHUNK_SIZE);\ + if (!smallmap_is_marked(M, I))\ + mark_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, B->fd)))\ + F = B->fd;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + B->fd = P;\ + F->bk = P;\ + P->fd = F;\ + P->bk = B;\ +} + +/* Unlink a chunk from a smallbin */ +#define unlink_small_chunk(M, P, S) {\ + mchunkptr F = P->fd;\ + mchunkptr B = P->bk;\ + bindex_t I = small_index(S);\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (F == B)\ + clear_smallmap(M, I);\ + else if (RTCHECK((F == smallbin_at(M,I) || ok_address(M, F)) &&\ + (B == smallbin_at(M,I) || ok_address(M, B)))) {\ + F->bk = B;\ + B->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + +/* Unlink the first chunk from a smallbin */ +#define unlink_first_small_chunk(M, B, P, I) {\ + mchunkptr F = P->fd;\ + assert(P != B);\ + assert(P != F);\ + assert(chunksize(P) == small_index2size(I));\ + if (B == F)\ + clear_smallmap(M, I);\ + else if (RTCHECK(ok_address(M, F))) {\ + B->fd = F;\ + F->bk = B;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ +} + + + +/* Replace dv node, binning the old one */ +/* Used only when dvsize known to be small */ +#define replace_dv(M, P, S) {\ + size_t DVS = M->dvsize;\ + if (DVS != 0) {\ + mchunkptr DV = M->dv;\ + assert(is_small(DVS));\ + insert_small_chunk(M, DV, DVS);\ + }\ + M->dvsize = S;\ + M->dv = P;\ +} + +/* ------------------------- Operations on trees ------------------------- */ + +/* Insert chunk into tree */ +#define insert_large_chunk(M, X, S) {\ + tbinptr* H;\ + bindex_t I;\ + compute_tree_index(S, I);\ + H = treebin_at(M, I);\ + X->index = I;\ + X->child[0] = X->child[1] = 0;\ + if (!treemap_is_marked(M, I)) {\ + mark_treemap(M, I);\ + *H = X;\ + X->parent = (tchunkptr)H;\ + X->fd = X->bk = X;\ + }\ + else {\ + tchunkptr T = *H;\ + size_t K = S << leftshift_for_tree_index(I);\ + for (;;) {\ + if (chunksize(T) != S) {\ + tchunkptr* C = &(T->child[(K >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]);\ + K <<= 1;\ + if (*C != 0)\ + T = *C;\ + else if (RTCHECK(ok_address(M, C))) {\ + *C = X;\ + X->parent = T;\ + X->fd = X->bk = X;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + else {\ + tchunkptr F = T->fd;\ + if (RTCHECK(ok_address(M, T) && ok_address(M, F))) {\ + T->fd = F->bk = X;\ + X->fd = F;\ + X->bk = T;\ + X->parent = 0;\ + break;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + break;\ + }\ + }\ + }\ + }\ +} + +/* + Unlink steps: + + 1. If x is a chained node, unlink it from its same-sized fd/bk links + and choose its bk node as its replacement. + 2. If x was the last node of its size, but not a leaf node, it must + be replaced with a leaf node (not merely one with an open left or + right), to make sure that lefts and rights of descendents + correspond properly to bit masks. We use the rightmost descendent + of x. We could use any other leaf, but this is easy to locate and + tends to counteract removal of leftmosts elsewhere, and so keeps + paths shorter than minimally guaranteed. This doesn't loop much + because on average a node in a tree is near the bottom. + 3. If x is the base of a chain (i.e., has parent links) relink + x's parent and children to x's replacement (or null if none). +*/ + +#define unlink_large_chunk(M, X) {\ + tchunkptr XP = X->parent;\ + tchunkptr R;\ + if (X->bk != X) {\ + tchunkptr F = X->fd;\ + R = X->bk;\ + if (RTCHECK(ok_address(M, F))) {\ + F->bk = R;\ + R->fd = F;\ + }\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else {\ + tchunkptr* RP;\ + if (((R = *(RP = &(X->child[1]))) != 0) ||\ + ((R = *(RP = &(X->child[0]))) != 0)) {\ + tchunkptr* CP;\ + while ((*(CP = &(R->child[1])) != 0) ||\ + (*(CP = &(R->child[0])) != 0)) {\ + R = *(RP = CP);\ + }\ + if (RTCHECK(ok_address(M, RP)))\ + *RP = 0;\ + else {\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + }\ + if (XP != 0) {\ + tbinptr* H = treebin_at(M, X->index);\ + if (X == *H) {\ + if ((*H = R) == 0) \ + clear_treemap(M, X->index);\ + }\ + else if (RTCHECK(ok_address(M, XP))) {\ + if (XP->child[0] == X) \ + XP->child[0] = R;\ + else \ + XP->child[1] = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + if (R != 0) {\ + if (RTCHECK(ok_address(M, R))) {\ + tchunkptr C0, C1;\ + R->parent = XP;\ + if ((C0 = X->child[0]) != 0) {\ + if (RTCHECK(ok_address(M, C0))) {\ + R->child[0] = C0;\ + C0->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + if ((C1 = X->child[1]) != 0) {\ + if (RTCHECK(ok_address(M, C1))) {\ + R->child[1] = C1;\ + C1->parent = R;\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ + else\ + CORRUPTION_ERROR_ACTION(M);\ + }\ + }\ +} + +/* Relays to large vs small bin operations */ + +#define insert_chunk(M, P, S)\ + if (is_small(S)) insert_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); insert_large_chunk(M, TP, S); } + +#define unlink_chunk(M, P, S)\ + if (is_small(S)) unlink_small_chunk(M, P, S)\ + else { tchunkptr TP = (tchunkptr)(P); unlink_large_chunk(M, TP); } + + +/* Relays to internal calls to malloc/free from realloc, memalign etc */ + +#if ONLY_MSPACES +#define internal_malloc(m, b) mspace_malloc(m, b) +#define internal_free(m, mem) mspace_free(m,mem); +#else /* ONLY_MSPACES */ +#if MSPACES +#define internal_malloc(m, b)\ + (m == gm)? dlmalloc(b) : mspace_malloc(m, b) +#define internal_free(m, mem)\ + if (m == gm) dlfree(mem); else mspace_free(m,mem); +#else /* MSPACES */ +#define internal_malloc(m, b) dlmalloc(b) +#define internal_free(m, mem) dlfree(mem) +#endif /* MSPACES */ +#endif /* ONLY_MSPACES */ + +/* ----------------------- Direct-mmapping chunks ----------------------- */ + +/* + Directly mmapped chunks are set up with an offset to the start of + the mmapped region stored in the prev_foot field of the chunk. This + allows reconstruction of the required argument to MUNMAP when freed, + and also allows adjustment of the returned chunk to meet alignment + requirements (especially in memalign). There is also enough space + allocated to hold a fake next chunk of size SIZE_T_SIZE to maintain + the PINUSE bit so frees can be checked. +*/ + +/* Malloc using mmap */ +static void* mmap_alloc(mstate m, size_t nb) { + size_t mmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + if (mmsize > nb) { /* Check for wrap around 0 */ + char* mm = (char*)(CALL_DIRECT_MMAP(mmsize)); + if (mm != CMFAIL) { + size_t offset = align_offset(chunk2mem(mm)); + size_t psize = mmsize - offset - MMAP_FOOT_PAD; + mchunkptr p = (mchunkptr)(mm + offset); + p->prev_foot = offset | IS_MMAPPED_BIT; + (p)->head = (psize|CINUSE_BIT); + mark_inuse_foot(m, p, psize); + chunk_plus_offset(p, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(p, psize+SIZE_T_SIZE)->head = 0; + + if (mm < m->least_addr) + m->least_addr = mm; + if ((m->footprint += mmsize) > m->max_footprint) + m->max_footprint = m->footprint; + assert(is_aligned(chunk2mem(p))); + check_mmapped_chunk(m, p); + return chunk2mem(p); + } + } + return 0; +} + +/* Realloc using mmap */ +static mchunkptr mmap_resize(mstate m, mchunkptr oldp, size_t nb) { + size_t oldsize = chunksize(oldp); + if (is_small(nb)) /* Can't shrink mmap regions below small size */ + return 0; + /* Keep old chunk if big enough but not too big */ + if (oldsize >= nb + SIZE_T_SIZE && + (oldsize - nb) <= (mparams.granularity << 1)) + return oldp; + else { + size_t offset = oldp->prev_foot & ~IS_MMAPPED_BIT; + size_t oldmmsize = oldsize + offset + MMAP_FOOT_PAD; + size_t newmmsize = mmap_align(nb + SIX_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + char* cp = (char*)CALL_MREMAP((char*)oldp - offset, + oldmmsize, newmmsize, 1); + if (cp != CMFAIL) { + mchunkptr newp = (mchunkptr)(cp + offset); + size_t psize = newmmsize - offset - MMAP_FOOT_PAD; + newp->head = (psize|CINUSE_BIT); + mark_inuse_foot(m, newp, psize); + chunk_plus_offset(newp, psize)->head = FENCEPOST_HEAD; + chunk_plus_offset(newp, psize+SIZE_T_SIZE)->head = 0; + + if (cp < m->least_addr) + m->least_addr = cp; + if ((m->footprint += newmmsize - oldmmsize) > m->max_footprint) + m->max_footprint = m->footprint; + check_mmapped_chunk(m, newp); + return newp; + } + } + return 0; +} + +/* -------------------------- mspace management -------------------------- */ + +/* Initialize top chunk and its size */ +static void init_top(mstate m, mchunkptr p, size_t psize) { + /* Ensure alignment */ + size_t offset = align_offset(chunk2mem(p)); + p = (mchunkptr)((char*)p + offset); + psize -= offset; + + m->top = p; + m->topsize = psize; + p->head = psize | PINUSE_BIT; + /* set size of fake trailing chunk holding overhead space only once */ + chunk_plus_offset(p, psize)->head = TOP_FOOT_SIZE; + m->trim_check = mparams.trim_threshold; /* reset on each update */ +} + +/* Initialize bins for a new mstate that is otherwise zeroed out */ +static void init_bins(mstate m) { + /* Establish circular links for smallbins */ + bindex_t i; + for (i = 0; i < NSMALLBINS; ++i) { + sbinptr bin = smallbin_at(m,i); + bin->fd = bin->bk = bin; + } +} + +#if PROCEED_ON_ERROR + +/* default corruption action */ +static void reset_on_error(mstate m) { + int i; + ++malloc_corruption_error_count; + /* Reinitialize fields to forget about all memory */ + m->smallbins = m->treebins = 0; + m->dvsize = m->topsize = 0; + m->seg.base = 0; + m->seg.size = 0; + m->seg.next = 0; + m->top = m->dv = 0; + for (i = 0; i < NTREEBINS; ++i) + *treebin_at(m, i) = 0; + init_bins(m); +} +#endif /* PROCEED_ON_ERROR */ + +/* Allocate chunk and prepend remainder with chunk in successor base. */ +static void* prepend_alloc(mstate m, char* newbase, char* oldbase, + size_t nb) { + mchunkptr p = align_as_chunk(newbase); + mchunkptr oldfirst = align_as_chunk(oldbase); + size_t psize = (char*)oldfirst - (char*)p; + mchunkptr q = chunk_plus_offset(p, nb); + size_t qsize = psize - nb; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + + assert((char*)oldfirst > (char*)q); + assert(pinuse(oldfirst)); + assert(qsize >= MIN_CHUNK_SIZE); + + /* consolidate remainder with first chunk of old base */ + if (oldfirst == m->top) { + size_t tsize = m->topsize += qsize; + m->top = q; + q->head = tsize | PINUSE_BIT; + check_top_chunk(m, q); + } + else if (oldfirst == m->dv) { + size_t dsize = m->dvsize += qsize; + m->dv = q; + set_size_and_pinuse_of_free_chunk(q, dsize); + } + else { + if (!cinuse(oldfirst)) { + size_t nsize = chunksize(oldfirst); + unlink_chunk(m, oldfirst, nsize); + oldfirst = chunk_plus_offset(oldfirst, nsize); + qsize += nsize; + } + set_free_with_pinuse(q, qsize, oldfirst); + insert_chunk(m, q, qsize); + check_free_chunk(m, q); + } + + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); +} + +/* Add a segment to hold a new noncontiguous region */ +static void add_segment(mstate m, char* tbase, size_t tsize, flag_t mmapped) { + /* Determine locations and sizes of segment, fenceposts, old top */ + char* old_top = (char*)m->top; + msegmentptr oldsp = segment_holding(m, old_top); + char* old_end = oldsp->base + oldsp->size; + size_t ssize = pad_request(sizeof(struct malloc_segment)); + char* rawsp = old_end - (ssize + FOUR_SIZE_T_SIZES + CHUNK_ALIGN_MASK); + size_t offset = align_offset(chunk2mem(rawsp)); + char* asp = rawsp + offset; + char* csp = (asp < (old_top + MIN_CHUNK_SIZE))? old_top : asp; + mchunkptr sp = (mchunkptr)csp; + msegmentptr ss = (msegmentptr)(chunk2mem(sp)); + mchunkptr tnext = chunk_plus_offset(sp, ssize); + mchunkptr p = tnext; + int nfences = 0; + + /* reset top to new space */ + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + + /* Set up segment record */ + assert(is_aligned(ss)); + set_size_and_pinuse_of_inuse_chunk(m, sp, ssize); + *ss = m->seg; /* Push current record */ + m->seg.base = tbase; + m->seg.size = tsize; + m->seg.sflags = mmapped; + m->seg.next = ss; + + /* Insert trailing fenceposts */ + for (;;) { + mchunkptr nextp = chunk_plus_offset(p, SIZE_T_SIZE); + p->head = FENCEPOST_HEAD; + ++nfences; + if ((char*)(&(nextp->head)) < old_end) + p = nextp; + else + break; + } + assert(nfences >= 2); + + /* Insert the rest of old top into a bin as an ordinary free chunk */ + if (csp != old_top) { + mchunkptr q = (mchunkptr)old_top; + size_t psize = csp - old_top; + mchunkptr tn = chunk_plus_offset(q, psize); + set_free_with_pinuse(q, psize, tn); + insert_chunk(m, q, psize); + } + + check_top_chunk(m, m->top); +} + +/* -------------------------- System allocation -------------------------- */ + +/* Get memory from system using MORECORE or MMAP */ +static void* sys_alloc(mstate m, size_t nb) { + char* tbase = CMFAIL; + size_t tsize = 0; + flag_t mmap_flag = 0; + + ensure_initialization(); + + /* Directly map large chunks */ + if (use_mmap(m) && nb >= mparams.mmap_threshold) { + void* mem = mmap_alloc(m, nb); + if (mem != 0) + return mem; + } + + /* + Try getting memory in any of three ways (in most-preferred to + least-preferred order): + 1. A call to MORECORE that can normally contiguously extend memory. + (disabled if not MORECORE_CONTIGUOUS or not HAVE_MORECORE or + or main space is mmapped or a previous contiguous call failed) + 2. A call to MMAP new space (disabled if not HAVE_MMAP). + Note that under the default settings, if MORECORE is unable to + fulfill a request, and HAVE_MMAP is true, then mmap is + used as a noncontiguous system allocator. This is a useful backup + strategy for systems with holes in address spaces -- in this case + sbrk cannot contiguously expand the heap, but mmap may be able to + find space. + 3. A call to MORECORE that cannot usually contiguously extend memory. + (disabled if not HAVE_MORECORE) + + In all cases, we need to request enough bytes from system to ensure + we can malloc nb bytes upon success, so pad with enough space for + top_foot, plus alignment-pad to make sure we don't lose bytes if + not on boundary, and round this up to a granularity unit. + */ + + if (MORECORE_CONTIGUOUS && !use_noncontiguous(m)) { + char* br = CMFAIL; + msegmentptr ss = (m->top == 0)? 0 : segment_holding(m, (char*)m->top); + size_t asize = 0; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + + if (ss == 0) { /* First time through or recovery */ + char* base = (char*)CALL_MORECORE(0); + if (base != CMFAIL) { + asize = granularity_align(nb + SYS_ALLOC_PADDING); + /* Adjust to end on a page boundary */ + if (!is_page_aligned(base)) + asize += (page_align((size_t)base) - (size_t)base); + /* Can't call MORECORE if size is negative when treated as signed */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == base) { + tbase = base; + tsize = asize; + } + } + } + else { + /* Subtract out existing available top space from MORECORE request. */ + asize = granularity_align(nb - m->topsize + SYS_ALLOC_PADDING); + /* Use mem here only if it did continuously extend old space */ + if (asize < HALF_MAX_SIZE_T && + (br = (char*)(CALL_MORECORE(asize))) == ss->base+ss->size) { + tbase = br; + tsize = asize; + } + } + + if (tbase == CMFAIL) { /* Cope with partial failure */ + if (br != CMFAIL) { /* Try to use/extend the space we did get */ + if (asize < HALF_MAX_SIZE_T && + asize < nb + SYS_ALLOC_PADDING) { + size_t esize = granularity_align(nb + SYS_ALLOC_PADDING - asize); + if (esize < HALF_MAX_SIZE_T) { + char* end = (char*)CALL_MORECORE(esize); + if (end != CMFAIL) + asize += esize; + else { /* Can't use; try to release */ + (void) CALL_MORECORE(-asize); + br = CMFAIL; + } + } + } + } + if (br != CMFAIL) { /* Use the space we did get */ + tbase = br; + tsize = asize; + } + else + disable_contiguous(m); /* Don't try contiguous path in the future */ + } + + RELEASE_MALLOC_GLOBAL_LOCK(); + } + + if (HAVE_MMAP && tbase == CMFAIL) { /* Try MMAP */ + size_t rsize = granularity_align(nb + SYS_ALLOC_PADDING); + if (rsize > nb) { /* Fail if wraps around zero */ + char* mp = (char*)(CALL_MMAP(rsize)); + if (mp != CMFAIL) { + tbase = mp; + tsize = rsize; + mmap_flag = IS_MMAPPED_BIT; + } + } + } + + if (HAVE_MORECORE && tbase == CMFAIL) { /* Try noncontiguous MORECORE */ + size_t asize = granularity_align(nb + SYS_ALLOC_PADDING); + if (asize < HALF_MAX_SIZE_T) { + char* br = CMFAIL; + char* end = CMFAIL; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + br = (char*)(CALL_MORECORE(asize)); + end = (char*)(CALL_MORECORE(0)); + RELEASE_MALLOC_GLOBAL_LOCK(); + if (br != CMFAIL && end != CMFAIL && br < end) { + size_t ssize = end - br; + if (ssize > nb + TOP_FOOT_SIZE) { + tbase = br; + tsize = ssize; + } + } + } + } + + if (tbase != CMFAIL) { + + if ((m->footprint += tsize) > m->max_footprint) + m->max_footprint = m->footprint; + + if (!is_initialized(m)) { /* first-time initialization */ + m->seg.base = m->least_addr = tbase; + m->seg.size = tsize; + m->seg.sflags = mmap_flag; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + init_bins(m); +#if !ONLY_MSPACES + if (is_global(m)) + init_top(m, (mchunkptr)tbase, tsize - TOP_FOOT_SIZE); + else +#endif + { + /* Offset top by embedded malloc_state */ + mchunkptr mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) -TOP_FOOT_SIZE); + } + } + + else { + /* Try to merge with an existing segment */ + msegmentptr sp = &m->seg; + /* Only consider most recent segment if traversal suppressed */ + while (sp != 0 && tbase != sp->base + sp->size) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & IS_MMAPPED_BIT) == mmap_flag && + segment_holds(sp, m->top)) { /* append */ + sp->size += tsize; + init_top(m, m->top, m->topsize + tsize); + } + else { + if (tbase < m->least_addr) + m->least_addr = tbase; + sp = &m->seg; + while (sp != 0 && sp->base != tbase + tsize) + sp = (NO_SEGMENT_TRAVERSAL) ? 0 : sp->next; + if (sp != 0 && + !is_extern_segment(sp) && + (sp->sflags & IS_MMAPPED_BIT) == mmap_flag) { + char* oldbase = sp->base; + sp->base = tbase; + sp->size += tsize; + return prepend_alloc(m, tbase, oldbase, nb); + } + else + add_segment(m, tbase, tsize, mmap_flag); + } + } + + if (nb < m->topsize) { /* Allocate from new or extended top space */ + size_t rsize = m->topsize -= nb; + mchunkptr p = m->top; + mchunkptr r = m->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(m, p, nb); + check_top_chunk(m, m->top); + check_malloced_chunk(m, chunk2mem(p), nb); + return chunk2mem(p); + } + } + + MALLOC_FAILURE_ACTION; + return 0; +} + +/* ----------------------- system deallocation -------------------------- */ + +/* Unmap and unlink any mmapped segments that don't contain used chunks */ +static size_t release_unused_segments(mstate m) { + size_t released = 0; + int nsegs = 0; + msegmentptr pred = &m->seg; + msegmentptr sp = pred->next; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + msegmentptr next = sp->next; + ++nsegs; + if (is_mmapped_segment(sp) && !is_extern_segment(sp)) { + mchunkptr p = align_as_chunk(base); + size_t psize = chunksize(p); + /* Can unmap if first chunk holds entire segment and not pinned */ + if (!cinuse(p) && (char*)p + psize >= base + size - TOP_FOOT_SIZE) { + tchunkptr tp = (tchunkptr)p; + assert(segment_holds(sp, (char*)sp)); + if (p == m->dv) { + m->dv = 0; + m->dvsize = 0; + } + else { + unlink_large_chunk(m, tp); + } + if (CALL_MUNMAP(base, size) == 0) { + released += size; + m->footprint -= size; + /* unlink obsoleted record */ + sp = pred; + sp->next = next; + } + else { /* back out if cannot unmap */ + insert_large_chunk(m, tp, psize); + } + } + } + if (NO_SEGMENT_TRAVERSAL) /* scan only first segment */ + break; + pred = sp; + sp = next; + } + /* Reset check counter */ + m->release_checks = ((nsegs > MAX_RELEASE_CHECK_RATE)? + nsegs : MAX_RELEASE_CHECK_RATE); + return released; +} + +static int sys_trim(mstate m, size_t pad) { + size_t released = 0; + ensure_initialization(); + if (pad < MAX_REQUEST && is_initialized(m)) { + pad += TOP_FOOT_SIZE; /* ensure enough room for segment overhead */ + + if (m->topsize > pad) { + /* Shrink top space in granularity-size units, keeping at least one */ + size_t unit = mparams.granularity; + size_t extra = ((m->topsize - pad + (unit - SIZE_T_ONE)) / unit - + SIZE_T_ONE) * unit; + msegmentptr sp = segment_holding(m, (char*)m->top); + + if (!is_extern_segment(sp)) { + if (is_mmapped_segment(sp)) { + if (HAVE_MMAP && + sp->size >= extra && + !has_segment_link(m, sp)) { /* can't shrink if pinned */ + size_t newsize = sp->size - extra; + /* Prefer mremap, fall back to munmap */ + if ((CALL_MREMAP(sp->base, sp->size, newsize, 0) != MFAIL) || + (CALL_MUNMAP(sp->base + newsize, extra) == 0)) { + released = extra; + } + } + } + else if (HAVE_MORECORE) { + if (extra >= HALF_MAX_SIZE_T) /* Avoid wrapping negative */ + extra = (HALF_MAX_SIZE_T) + SIZE_T_ONE - unit; + ACQUIRE_MALLOC_GLOBAL_LOCK(); + { + /* Make sure end of memory is where we last set it. */ + char* old_br = (char*)(CALL_MORECORE(0)); + if (old_br == sp->base + sp->size) { + char* rel_br = (char*)(CALL_MORECORE(-extra)); + char* new_br = (char*)(CALL_MORECORE(0)); + if (rel_br != CMFAIL && new_br < old_br) + released = old_br - new_br; + } + } + RELEASE_MALLOC_GLOBAL_LOCK(); + } + } + + if (released != 0) { + sp->size -= released; + m->footprint -= released; + init_top(m, m->top, m->topsize - released); + check_top_chunk(m, m->top); + } + } + + /* Unmap any unused mmapped segments */ + if (HAVE_MMAP) + released += release_unused_segments(m); + + /* On failure, disable autotrim to avoid repeated failed future calls */ + if (released == 0 && m->topsize > m->trim_check) + m->trim_check = MAX_SIZE_T; + } + + return (released != 0)? 1 : 0; +} + + +/* ---------------------------- malloc support --------------------------- */ + +/* allocate a large request from the best fitting chunk in a treebin */ +static void* tmalloc_large(mstate m, size_t nb) { + tchunkptr v = 0; + size_t rsize = -nb; /* Unsigned negation */ + tchunkptr t; + bindex_t idx; + compute_tree_index(nb, idx); + if ((t = *treebin_at(m, idx)) != 0) { + /* Traverse tree for this bin looking for node with size == nb */ + size_t sizebits = nb << leftshift_for_tree_index(idx); + tchunkptr rst = 0; /* The deepest untaken right subtree */ + for (;;) { + tchunkptr rt; + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + v = t; + if ((rsize = trem) == 0) + break; + } + rt = t->child[1]; + t = t->child[(sizebits >> (SIZE_T_BITSIZE-SIZE_T_ONE)) & 1]; + if (rt != 0 && rt != t) + rst = rt; + if (t == 0) { + t = rst; /* set t to least subtree holding sizes > nb */ + break; + } + sizebits <<= 1; + } + } + if (t == 0 && v == 0) { /* set t to root of next non-empty treebin */ + binmap_t leftbits = left_bits(idx2bit(idx)) & m->treemap; + if (leftbits != 0) { + bindex_t i; + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + t = *treebin_at(m, i); + } + } + + while (t != 0) { /* find smallest of tree or subtree */ + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + t = leftmost_child(t); + } + + /* If dv is a better fit, return 0 so malloc will use it */ + if (v != 0 && rsize < (size_t)(m->dvsize - nb)) { + if (RTCHECK(ok_address(m, v))) { /* split */ + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + insert_chunk(m, r, rsize); + } + return chunk2mem(v); + } + } + CORRUPTION_ERROR_ACTION(m); + } + return 0; +} + +/* allocate a small request from the best fitting chunk in a treebin */ +static void* tmalloc_small(mstate m, size_t nb) { + tchunkptr t, v; + size_t rsize; + bindex_t i; + binmap_t leastbit = least_bit(m->treemap); + compute_bit2idx(leastbit, i); + v = t = *treebin_at(m, i); + rsize = chunksize(t) - nb; + + while ((t = leftmost_child(t)) != 0) { + size_t trem = chunksize(t) - nb; + if (trem < rsize) { + rsize = trem; + v = t; + } + } + + if (RTCHECK(ok_address(m, v))) { + mchunkptr r = chunk_plus_offset(v, nb); + assert(chunksize(v) == rsize + nb); + if (RTCHECK(ok_next(v, r))) { + unlink_large_chunk(m, v); + if (rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(m, v, (rsize + nb)); + else { + set_size_and_pinuse_of_inuse_chunk(m, v, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(m, r, rsize); + } + return chunk2mem(v); + } + } + + CORRUPTION_ERROR_ACTION(m); + return 0; +} + +/* --------------------------- realloc support --------------------------- */ + +static void* internal_realloc(mstate m, void* oldmem, size_t bytes) { + if (bytes >= MAX_REQUEST) { + MALLOC_FAILURE_ACTION; + return 0; + } + if (!PREACTION(m)) { + mchunkptr oldp = mem2chunk(oldmem); + size_t oldsize = chunksize(oldp); + mchunkptr next = chunk_plus_offset(oldp, oldsize); + mchunkptr newp = 0; + void* extra = 0; + + /* Try to either shrink or extend into top. Else malloc-copy-free */ + + if (RTCHECK(ok_address(m, oldp) && ok_cinuse(oldp) && + ok_next(oldp, next) && ok_pinuse(next))) { + size_t nb = request2size(bytes); + if (is_mmapped(oldp)) + newp = mmap_resize(m, oldp, nb); + else if (oldsize >= nb) { /* already big enough */ + size_t rsize = oldsize - nb; + newp = oldp; + if (rsize >= MIN_CHUNK_SIZE) { + mchunkptr remainder = chunk_plus_offset(newp, nb); + set_inuse(m, newp, nb); + set_inuse(m, remainder, rsize); + extra = chunk2mem(remainder); + } + } + else if (next == m->top && oldsize + m->topsize > nb) { + /* Expand into top */ + size_t newsize = oldsize + m->topsize; + size_t newtopsize = newsize - nb; + mchunkptr newtop = chunk_plus_offset(oldp, nb); + set_inuse(m, oldp, nb); + newtop->head = newtopsize |PINUSE_BIT; + m->top = newtop; + m->topsize = newtopsize; + newp = oldp; + } + } + else { + USAGE_ERROR_ACTION(m, oldmem); + POSTACTION(m); + return 0; + } + + POSTACTION(m); + + if (newp != 0) { + if (extra != 0) { + internal_free(m, extra); + } + check_inuse_chunk(m, newp); + return chunk2mem(newp); + } + else { + void* newmem = internal_malloc(m, bytes); + if (newmem != 0) { + size_t oc = oldsize - overhead_for(oldp); + memcpy(newmem, oldmem, (oc < bytes)? oc : bytes); + internal_free(m, oldmem); + } + return newmem; + } + } + return 0; +} + +/* --------------------------- memalign support -------------------------- */ + +static void* internal_memalign(mstate m, size_t alignment, size_t bytes) { + if (alignment <= MALLOC_ALIGNMENT) /* Can just use malloc */ + return internal_malloc(m, bytes); + if (alignment < MIN_CHUNK_SIZE) /* must be at least a minimum chunk size */ + alignment = MIN_CHUNK_SIZE; + if ((alignment & (alignment-SIZE_T_ONE)) != 0) {/* Ensure a power of 2 */ + size_t a = MALLOC_ALIGNMENT << 1; + while (a < alignment) a <<= 1; + alignment = a; + } + + if (bytes >= MAX_REQUEST - alignment) { + if (m != 0) { /* Test isn't needed but avoids compiler warning */ + MALLOC_FAILURE_ACTION; + } + } + else { + size_t nb = request2size(bytes); + size_t req = nb + alignment + MIN_CHUNK_SIZE - CHUNK_OVERHEAD; + char* mem = (char*)internal_malloc(m, req); + if (mem != 0) { + void* leader = 0; + void* trailer = 0; + mchunkptr p = mem2chunk(mem); + + if (PREACTION(m)) return 0; + if ((((size_t)(mem)) % alignment) != 0) { /* misaligned */ + /* + Find an aligned spot inside chunk. Since we need to give + back leading space in a chunk of at least MIN_CHUNK_SIZE, if + the first calculation places us at a spot with less than + MIN_CHUNK_SIZE leader, we can move to the next aligned spot. + We've allocated enough total room so that this is always + possible. + */ + char* br = (char*)mem2chunk((size_t)(((size_t)(mem + + alignment - + SIZE_T_ONE)) & + -alignment)); + char* pos = ((size_t)(br - (char*)(p)) >= MIN_CHUNK_SIZE)? + br : br+alignment; + mchunkptr newp = (mchunkptr)pos; + size_t leadsize = pos - (char*)(p); + size_t newsize = chunksize(p) - leadsize; + + if (is_mmapped(p)) { /* For mmapped chunks, just adjust offset */ + newp->prev_foot = p->prev_foot + leadsize; + newp->head = (newsize|CINUSE_BIT); + } + else { /* Otherwise, give back leader, use the rest */ + set_inuse(m, newp, newsize); + set_inuse(m, p, leadsize); + leader = chunk2mem(p); + } + p = newp; + } + + /* Give back spare room at the end */ + if (!is_mmapped(p)) { + size_t size = chunksize(p); + if (size > nb + MIN_CHUNK_SIZE) { + size_t remainder_size = size - nb; + mchunkptr remainder = chunk_plus_offset(p, nb); + set_inuse(m, p, nb); + set_inuse(m, remainder, remainder_size); + trailer = chunk2mem(remainder); + } + } + + assert (chunksize(p) >= nb); + assert((((size_t)(chunk2mem(p))) % alignment) == 0); + check_inuse_chunk(m, p); + POSTACTION(m); + if (leader != 0) { + internal_free(m, leader); + } + if (trailer != 0) { + internal_free(m, trailer); + } + return chunk2mem(p); + } + } + return 0; +} + +/* ------------------------ comalloc/coalloc support --------------------- */ + +static void** ialloc(mstate m, + size_t n_elements, + size_t* sizes, + int opts, + void* chunks[]) { + /* + This provides common support for independent_X routines, handling + all of the combinations that can result. + + The opts arg has: + bit 0 set if all elements are same size (using sizes[0]) + bit 1 set if elements should be zeroed + */ + + size_t element_size; /* chunksize of each element, if all same */ + size_t contents_size; /* total size of elements */ + size_t array_size; /* request size of pointer array */ + void* mem; /* malloced aggregate space */ + mchunkptr p; /* corresponding chunk */ + size_t remainder_size; /* remaining bytes while splitting */ + void** marray; /* either "chunks" or malloced ptr array */ + mchunkptr array_chunk; /* chunk for malloced ptr array */ + flag_t was_enabled; /* to disable mmap */ + size_t size; + size_t i; + + ensure_initialization(); + /* compute array length, if needed */ + if (chunks != 0) { + if (n_elements == 0) + return chunks; /* nothing to do */ + marray = chunks; + array_size = 0; + } + else { + /* if empty req, must still return chunk representing empty array */ + if (n_elements == 0) + return (void**)internal_malloc(m, 0); + marray = 0; + array_size = request2size(n_elements * (sizeof(void*))); + } + + /* compute total element size */ + if (opts & 0x1) { /* all-same-size */ + element_size = request2size(*sizes); + contents_size = n_elements * element_size; + } + else { /* add up all the sizes */ + element_size = 0; + contents_size = 0; + for (i = 0; i != n_elements; ++i) + contents_size += request2size(sizes[i]); + } + + size = contents_size + array_size; + + /* + Allocate the aggregate chunk. First disable direct-mmapping so + malloc won't use it, since we would not be able to later + free/realloc space internal to a segregated mmap region. + */ + was_enabled = use_mmap(m); + disable_mmap(m); + mem = internal_malloc(m, size - CHUNK_OVERHEAD); + if (was_enabled) + enable_mmap(m); + if (mem == 0) + return 0; + + if (PREACTION(m)) return 0; + p = mem2chunk(mem); + remainder_size = chunksize(p); + + assert(!is_mmapped(p)); + + if (opts & 0x2) { /* optionally clear the elements */ + memset((size_t*)mem, 0, remainder_size - SIZE_T_SIZE - array_size); + } + + /* If not provided, allocate the pointer array as final part of chunk */ + if (marray == 0) { + size_t array_chunk_size; + array_chunk = chunk_plus_offset(p, contents_size); + array_chunk_size = remainder_size - contents_size; + marray = (void**) (chunk2mem(array_chunk)); + set_size_and_pinuse_of_inuse_chunk(m, array_chunk, array_chunk_size); + remainder_size = contents_size; + } + + /* split out elements */ + for (i = 0; ; ++i) { + marray[i] = chunk2mem(p); + if (i != n_elements-1) { + if (element_size != 0) + size = element_size; + else + size = request2size(sizes[i]); + remainder_size -= size; + set_size_and_pinuse_of_inuse_chunk(m, p, size); + p = chunk_plus_offset(p, size); + } + else { /* the final element absorbs any overallocation slop */ + set_size_and_pinuse_of_inuse_chunk(m, p, remainder_size); + break; + } + } + +#if DEBUG + if (marray != chunks) { + /* final element must have exactly exhausted chunk */ + if (element_size != 0) { + assert(remainder_size == element_size); + } + else { + assert(remainder_size == request2size(sizes[i])); + } + check_inuse_chunk(m, mem2chunk(marray)); + } + for (i = 0; i != n_elements; ++i) + check_inuse_chunk(m, mem2chunk(marray[i])); + +#endif /* DEBUG */ + + POSTACTION(m); + return marray; +} + + +/* -------------------------- public routines ---------------------------- */ + +#if !ONLY_MSPACES + +void* dlmalloc(size_t bytes) { + /* + Basic algorithm: + If a small request (< 256 bytes minus per-chunk overhead): + 1. If one exists, use a remainderless chunk in associated smallbin. + (Remainderless means that there are too few excess bytes to + represent as a chunk.) + 2. If it is big enough, use the dv chunk, which is normally the + chunk adjacent to the one used for the most recent small request. + 3. If one exists, split the smallest available chunk in a bin, + saving remainder in dv. + 4. If it is big enough, use the top chunk. + 5. If available, get memory from system and use it + Otherwise, for a large request: + 1. Find the smallest available binned chunk that fits, and use it + if it is better fitting than dv chunk, splitting if necessary. + 2. If better fitting than any binned chunk, use the dv chunk. + 3. If it is big enough, use the top chunk. + 4. If request size >= mmap threshold, try to directly mmap this chunk. + 5. If available, get memory from system and use it + + The ugly goto's here ensure that postaction occurs along all paths. + */ + +#if USE_LOCKS + ensure_initialization(); /* initialize in sys_alloc if not using locks */ +#endif + + if (!PREACTION(gm)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = gm->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(gm, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(gm, b, p, idx); + set_inuse_and_pinuse(gm, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb > gm->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(gm, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(gm, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(gm, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(gm, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (gm->treemap != 0 && (mem = tmalloc_small(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (gm->treemap != 0 && (mem = tmalloc_large(gm, nb)) != 0) { + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + } + + if (nb <= gm->dvsize) { + size_t rsize = gm->dvsize - nb; + mchunkptr p = gm->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = gm->dv = chunk_plus_offset(p, nb); + gm->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + } + else { /* exhaust dv */ + size_t dvs = gm->dvsize; + gm->dvsize = 0; + gm->dv = 0; + set_inuse_and_pinuse(gm, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + else if (nb < gm->topsize) { /* Split top */ + size_t rsize = gm->topsize -= nb; + mchunkptr p = gm->top; + mchunkptr r = gm->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(gm, p, nb); + mem = chunk2mem(p); + check_top_chunk(gm, gm->top); + check_malloced_chunk(gm, mem, nb); + goto postaction; + } + + mem = sys_alloc(gm, nb); + + postaction: + POSTACTION(gm); + return mem; + } + + return 0; +} + +void dlfree(void* mem) { + /* + Consolidate freed chunks with preceeding or succeeding bordering + free chunks, if they exist, and then place in a bin. Intermixed + with special cases for top, dv, mmapped chunks, and usage errors. + */ + + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } +#else /* FOOTERS */ +#define fm gm +#endif /* FOOTERS */ + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if ((prevsize & IS_MMAPPED_BIT) != 0) { + prevsize &= ~IS_MMAPPED_BIT; + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +#if !FOOTERS +#undef fm +#endif /* FOOTERS */ +} + +void* dlcalloc(size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = dlmalloc(req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* dlrealloc(void* oldmem, size_t bytes) { + if (oldmem == 0) + return dlmalloc(bytes); +#ifdef REALLOC_ZERO_BYTES_FREES + if (bytes == 0) { + dlfree(oldmem); + return 0; + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { +#if ! FOOTERS + mstate m = gm; +#else /* FOOTERS */ + mstate m = get_mstate_for(mem2chunk(oldmem)); + if (!ok_magic(m)) { + USAGE_ERROR_ACTION(m, oldmem); + return 0; + } +#endif /* FOOTERS */ + return internal_realloc(m, oldmem, bytes); + } +} + +void* dlmemalign(size_t alignment, size_t bytes) { + return internal_memalign(gm, alignment, bytes); +} + +void** dlindependent_calloc(size_t n_elements, size_t elem_size, + void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + return ialloc(gm, n_elements, &sz, 3, chunks); +} + +void** dlindependent_comalloc(size_t n_elements, size_t sizes[], + void* chunks[]) { + return ialloc(gm, n_elements, sizes, 0, chunks); +} + +void* dlvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, bytes); +} + +void* dlpvalloc(size_t bytes) { + size_t pagesz; + ensure_initialization(); + pagesz = mparams.page_size; + return dlmemalign(pagesz, (bytes + pagesz - SIZE_T_ONE) & ~(pagesz - SIZE_T_ONE)); +} + +int dlmalloc_trim(size_t pad) { + ensure_initialization(); + int result = 0; + if (!PREACTION(gm)) { + result = sys_trim(gm, pad); + POSTACTION(gm); + } + return result; +} + +size_t dlmalloc_footprint(void) { + return gm->footprint; +} + +size_t dlmalloc_max_footprint(void) { + return gm->max_footprint; +} + +#if !NO_MALLINFO +struct mallinfo dlmallinfo(void) { + return internal_mallinfo(gm); +} +#endif /* NO_MALLINFO */ + +void dlmalloc_stats() { + internal_malloc_stats(gm); +} + +int dlmallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* !ONLY_MSPACES */ + +size_t dlmalloc_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (cinuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +/* ----------------------------- user mspaces ---------------------------- */ + +#if MSPACES + +static mstate init_user_mstate(char* tbase, size_t tsize) { + size_t msize = pad_request(sizeof(struct malloc_state)); + mchunkptr mn; + mchunkptr msp = align_as_chunk(tbase); + mstate m = (mstate)(chunk2mem(msp)); + memset(m, 0, msize); + INITIAL_LOCK(&m->mutex); + msp->head = (msize|PINUSE_BIT|CINUSE_BIT); + m->seg.base = m->least_addr = tbase; + m->seg.size = m->footprint = m->max_footprint = tsize; + m->magic = mparams.magic; + m->release_checks = MAX_RELEASE_CHECK_RATE; + m->mflags = mparams.default_mflags; + m->extp = 0; + m->exts = 0; + disable_contiguous(m); + init_bins(m); + mn = next_chunk(mem2chunk(m)); + init_top(m, mn, (size_t)((tbase + tsize) - (char*)mn) - TOP_FOOT_SIZE); + check_top_chunk(m, m->top); + return m; +} + +mspace create_mspace(size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + size_t rs = ((capacity == 0)? mparams.granularity : + (capacity + TOP_FOOT_SIZE + msize)); + size_t tsize = granularity_align(rs); + char* tbase = (char*)(CALL_MMAP(tsize)); + if (tbase != CMFAIL) { + m = init_user_mstate(tbase, tsize); + m->seg.sflags = IS_MMAPPED_BIT; + set_lock(m, locked); + } + } + return (mspace)m; +} + +mspace create_mspace_with_base(void* base, size_t capacity, int locked) { + mstate m = 0; + size_t msize; + ensure_initialization(); + msize = pad_request(sizeof(struct malloc_state)); + if (capacity > msize + TOP_FOOT_SIZE && + capacity < (size_t) -(msize + TOP_FOOT_SIZE + mparams.page_size)) { + m = init_user_mstate((char*)base, capacity); + m->seg.sflags = EXTERN_BIT; + set_lock(m, locked); + } + return (mspace)m; +} + +int mspace_mmap_large_chunks(mspace msp, int enable) { + int ret = 0; + mstate ms = (mstate)msp; + if (!PREACTION(ms)) { + if (use_mmap(ms)) + ret = 1; + if (enable) + enable_mmap(ms); + else + disable_mmap(ms); + POSTACTION(ms); + } + return ret; +} + +size_t destroy_mspace(mspace msp) { + size_t freed = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + msegmentptr sp = &ms->seg; + while (sp != 0) { + char* base = sp->base; + size_t size = sp->size; + flag_t flag = sp->sflags; + sp = sp->next; + if ((flag & IS_MMAPPED_BIT) && !(flag & EXTERN_BIT) && + CALL_MUNMAP(base, size) == 0) + freed += size; + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return freed; +} + +/* + mspace versions of routines are near-clones of the global + versions. This is not so nice but better than the alternatives. +*/ + + +void* mspace_malloc(mspace msp, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (!PREACTION(ms)) { + void* mem; + size_t nb; + if (bytes <= MAX_SMALL_REQUEST) { + bindex_t idx; + binmap_t smallbits; + nb = (bytes < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(bytes); + idx = small_index(nb); + smallbits = ms->smallmap >> idx; + + if ((smallbits & 0x3U) != 0) { /* Remainderless fit to a smallbin. */ + mchunkptr b, p; + idx += ~smallbits & 1; /* Uses next bin if idx empty */ + b = smallbin_at(ms, idx); + p = b->fd; + assert(chunksize(p) == small_index2size(idx)); + unlink_first_small_chunk(ms, b, p, idx); + set_inuse_and_pinuse(ms, p, small_index2size(idx)); + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb > ms->dvsize) { + if (smallbits != 0) { /* Use chunk in next nonempty smallbin */ + mchunkptr b, p, r; + size_t rsize; + bindex_t i; + binmap_t leftbits = (smallbits << idx) & left_bits(idx2bit(idx)); + binmap_t leastbit = least_bit(leftbits); + compute_bit2idx(leastbit, i); + b = smallbin_at(ms, i); + p = b->fd; + assert(chunksize(p) == small_index2size(i)); + unlink_first_small_chunk(ms, b, p, i); + rsize = small_index2size(i) - nb; + /* Fit here cannot be remainderless if 4byte sizes */ + if (SIZE_T_SIZE != 4 && rsize < MIN_CHUNK_SIZE) + set_inuse_and_pinuse(ms, p, small_index2size(i)); + else { + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + r = chunk_plus_offset(p, nb); + set_size_and_pinuse_of_free_chunk(r, rsize); + replace_dv(ms, r, rsize); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (ms->treemap != 0 && (mem = tmalloc_small(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + } + else if (bytes >= MAX_REQUEST) + nb = MAX_SIZE_T; /* Too big to allocate. Force failure (in sys alloc) */ + else { + nb = pad_request(bytes); + if (ms->treemap != 0 && (mem = tmalloc_large(ms, nb)) != 0) { + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + } + + if (nb <= ms->dvsize) { + size_t rsize = ms->dvsize - nb; + mchunkptr p = ms->dv; + if (rsize >= MIN_CHUNK_SIZE) { /* split dv */ + mchunkptr r = ms->dv = chunk_plus_offset(p, nb); + ms->dvsize = rsize; + set_size_and_pinuse_of_free_chunk(r, rsize); + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + } + else { /* exhaust dv */ + size_t dvs = ms->dvsize; + ms->dvsize = 0; + ms->dv = 0; + set_inuse_and_pinuse(ms, p, dvs); + } + mem = chunk2mem(p); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + else if (nb < ms->topsize) { /* Split top */ + size_t rsize = ms->topsize -= nb; + mchunkptr p = ms->top; + mchunkptr r = ms->top = chunk_plus_offset(p, nb); + r->head = rsize | PINUSE_BIT; + set_size_and_pinuse_of_inuse_chunk(ms, p, nb); + mem = chunk2mem(p); + check_top_chunk(ms, ms->top); + check_malloced_chunk(ms, mem, nb); + goto postaction; + } + + mem = sys_alloc(ms, nb); + + postaction: + POSTACTION(ms); + return mem; + } + + return 0; +} + +void mspace_free(mspace msp, void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); +#if FOOTERS + mstate fm = get_mstate_for(p); +#else /* FOOTERS */ + mstate fm = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + if (!PREACTION(fm)) { + check_inuse_chunk(fm, p); + if (RTCHECK(ok_address(fm, p) && ok_cinuse(p))) { + size_t psize = chunksize(p); + mchunkptr next = chunk_plus_offset(p, psize); + if (!pinuse(p)) { + size_t prevsize = p->prev_foot; + if ((prevsize & IS_MMAPPED_BIT) != 0) { + prevsize &= ~IS_MMAPPED_BIT; + psize += prevsize + MMAP_FOOT_PAD; + if (CALL_MUNMAP((char*)p - prevsize, psize) == 0) + fm->footprint -= psize; + goto postaction; + } + else { + mchunkptr prev = chunk_minus_offset(p, prevsize); + psize += prevsize; + p = prev; + if (RTCHECK(ok_address(fm, prev))) { /* consolidate backward */ + if (p != fm->dv) { + unlink_chunk(fm, p, prevsize); + } + else if ((next->head & INUSE_BITS) == INUSE_BITS) { + fm->dvsize = psize; + set_free_with_pinuse(p, psize, next); + goto postaction; + } + } + else + goto erroraction; + } + } + + if (RTCHECK(ok_next(p, next) && ok_pinuse(next))) { + if (!cinuse(next)) { /* consolidate forward */ + if (next == fm->top) { + size_t tsize = fm->topsize += psize; + fm->top = p; + p->head = tsize | PINUSE_BIT; + if (p == fm->dv) { + fm->dv = 0; + fm->dvsize = 0; + } + if (should_trim(fm, tsize)) + sys_trim(fm, 0); + goto postaction; + } + else if (next == fm->dv) { + size_t dsize = fm->dvsize += psize; + fm->dv = p; + set_size_and_pinuse_of_free_chunk(p, dsize); + goto postaction; + } + else { + size_t nsize = chunksize(next); + psize += nsize; + unlink_chunk(fm, next, nsize); + set_size_and_pinuse_of_free_chunk(p, psize); + if (p == fm->dv) { + fm->dvsize = psize; + goto postaction; + } + } + } + else + set_free_with_pinuse(p, psize, next); + + if (is_small(psize)) { + insert_small_chunk(fm, p, psize); + check_free_chunk(fm, p); + } + else { + tchunkptr tp = (tchunkptr)p; + insert_large_chunk(fm, tp, psize); + check_free_chunk(fm, p); + if (--fm->release_checks == 0) + release_unused_segments(fm); + } + goto postaction; + } + } + erroraction: + USAGE_ERROR_ACTION(fm, p); + postaction: + POSTACTION(fm); + } + } +} + +void* mspace_calloc(mspace msp, size_t n_elements, size_t elem_size) { + void* mem; + size_t req = 0; + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + if (n_elements != 0) { + req = n_elements * elem_size; + if (((n_elements | elem_size) & ~(size_t)0xffff) && + (req / n_elements != elem_size)) + req = MAX_SIZE_T; /* force downstream failure on overflow */ + } + mem = internal_malloc(ms, req); + if (mem != 0 && calloc_must_clear(mem2chunk(mem))) + memset(mem, 0, req); + return mem; +} + +void* mspace_realloc(mspace msp, void* oldmem, size_t bytes) { + if (oldmem == 0) + return mspace_malloc(msp, bytes); +#ifdef REALLOC_ZERO_BYTES_FREES + if (bytes == 0) { + mspace_free(msp, oldmem); + return 0; + } +#endif /* REALLOC_ZERO_BYTES_FREES */ + else { +#if FOOTERS + mchunkptr p = mem2chunk(oldmem); + mstate ms = get_mstate_for(p); +#else /* FOOTERS */ + mstate ms = (mstate)msp; +#endif /* FOOTERS */ + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return internal_realloc(ms, oldmem, bytes); + } +} + +void* mspace_memalign(mspace msp, size_t alignment, size_t bytes) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return internal_memalign(ms, alignment, bytes); +} + +void** mspace_independent_calloc(mspace msp, size_t n_elements, + size_t elem_size, void* chunks[]) { + size_t sz = elem_size; /* serves as 1-element array */ + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, &sz, 3, chunks); +} + +void** mspace_independent_comalloc(mspace msp, size_t n_elements, + size_t sizes[], void* chunks[]) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + return 0; + } + return ialloc(ms, n_elements, sizes, 0, chunks); +} + +int mspace_trim(mspace msp, size_t pad) { + int result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + if (!PREACTION(ms)) { + result = sys_trim(ms, pad); + POSTACTION(ms); + } + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + +void mspace_malloc_stats(mspace msp) { + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + internal_malloc_stats(ms); + } + else { + USAGE_ERROR_ACTION(ms,ms); + } +} + +size_t mspace_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + + +size_t mspace_max_footprint(mspace msp) { + size_t result = 0; + mstate ms = (mstate)msp; + if (ok_magic(ms)) { + result = ms->max_footprint; + } + else { + USAGE_ERROR_ACTION(ms,ms); + } + return result; +} + + +#if !NO_MALLINFO +struct mallinfo mspace_mallinfo(mspace msp) { + mstate ms = (mstate)msp; + if (!ok_magic(ms)) { + USAGE_ERROR_ACTION(ms,ms); + } + return internal_mallinfo(ms); +} +#endif /* NO_MALLINFO */ + +size_t mspace_usable_size(void* mem) { + if (mem != 0) { + mchunkptr p = mem2chunk(mem); + if (cinuse(p)) + return chunksize(p) - overhead_for(p); + } + return 0; +} + +int mspace_mallopt(int param_number, int value) { + return change_mparam(param_number, value); +} + +#endif /* MSPACES */ + +/* -------------------- Alternative MORECORE functions ------------------- */ + +/* + Guidelines for creating a custom version of MORECORE: + + * For best performance, MORECORE should allocate in multiples of pagesize. + * MORECORE may allocate more memory than requested. (Or even less, + but this will usually result in a malloc failure.) + * MORECORE must not allocate memory when given argument zero, but + instead return one past the end address of memory from previous + nonzero call. + * For best performance, consecutive calls to MORECORE with positive + arguments should return increasing addresses, indicating that + space has been contiguously extended. + * Even though consecutive calls to MORECORE need not return contiguous + addresses, it must be OK for malloc'ed chunks to span multiple + regions in those cases where they do happen to be contiguous. + * MORECORE need not handle negative arguments -- it may instead + just return MFAIL when given negative arguments. + Negative arguments are always multiples of pagesize. MORECORE + must not misinterpret negative args as large positive unsigned + args. You can suppress all such calls from even occurring by defining + MORECORE_CANNOT_TRIM, + + As an example alternative MORECORE, here is a custom allocator + kindly contributed for pre-OSX macOS. It uses virtually but not + necessarily physically contiguous non-paged memory (locked in, + present and won't get swapped out). You can use it by uncommenting + this section, adding some #includes, and setting up the appropriate + defines above: + + #define MORECORE osMoreCore + + There is also a shutdown routine that should somehow be called for + cleanup upon program exit. + + #define MAX_POOL_ENTRIES 100 + #define MINIMUM_MORECORE_SIZE (64 * 1024U) + static int next_os_pool; + void *our_os_pools[MAX_POOL_ENTRIES]; + + void *osMoreCore(int size) + { + void *ptr = 0; + static void *sbrk_top = 0; + + if (size > 0) + { + if (size < MINIMUM_MORECORE_SIZE) + size = MINIMUM_MORECORE_SIZE; + if (CurrentExecutionLevel() == kTaskLevel) + ptr = PoolAllocateResident(size + RM_PAGE_SIZE, 0); + if (ptr == 0) + { + return (void *) MFAIL; + } + // save ptrs so they can be freed during cleanup + our_os_pools[next_os_pool] = ptr; + next_os_pool++; + ptr = (void *) ((((size_t) ptr) + RM_PAGE_MASK) & ~RM_PAGE_MASK); + sbrk_top = (char *) ptr + size; + return ptr; + } + else if (size < 0) + { + // we don't currently support shrink behavior + return (void *) MFAIL; + } + else + { + return sbrk_top; + } + } + + // cleanup any allocated memory pools + // called as last thing before shutting down driver + + void osCleanupMem(void) + { + void **ptr; + + for (ptr = our_os_pools; ptr < &our_os_pools[MAX_POOL_ENTRIES]; ptr++) + if (*ptr) + { + PoolDeallocate(*ptr); + *ptr = 0; + } + } + +*/ + + +/* ----------------------------------------------------------------------- +History: + V2.8.4 (not yet released) + * Add mspace_mmap_large_chunks; thanks to Jean Brouwers + * Fix insufficient sys_alloc padding when using 16byte alignment + * Fix bad error check in mspace_footprint + * Adaptations for ptmalloc, courtesy of Wolfram Gloger. + * Reentrant spin locks, courtesy of Earl Chew and others + * Win32 improvements, courtesy of Niall Douglas and Earl Chew + * Add NO_SEGMENT_TRAVERSAL and MAX_RELEASE_CHECK_RATE options + * Extension hook in malloc_state + * Various small adjustments to reduce warnings on some compilers + * Various configuration extensions/changes for more platforms. Thanks + to all who contributed these. + + V2.8.3 Thu Sep 22 11:16:32 2005 Doug Lea (dl at gee) + * Add max_footprint functions + * Ensure all appropriate literals are size_t + * Fix conditional compilation problem for some #define settings + * Avoid concatenating segments with the one provided + in create_mspace_with_base + * Rename some variables to avoid compiler shadowing warnings + * Use explicit lock initialization. + * Better handling of sbrk interference. + * Simplify and fix segment insertion, trimming and mspace_destroy + * Reinstate REALLOC_ZERO_BYTES_FREES option from 2.7.x + * Thanks especially to Dennis Flanagan for help on these. + + V2.8.2 Sun Jun 12 16:01:10 2005 Doug Lea (dl at gee) + * Fix memalign brace error. + + V2.8.1 Wed Jun 8 16:11:46 2005 Doug Lea (dl at gee) + * Fix improper #endif nesting in C++ + * Add explicit casts needed for C++ + + V2.8.0 Mon May 30 14:09:02 2005 Doug Lea (dl at gee) + * Use trees for large bins + * Support mspaces + * Use segments to unify sbrk-based and mmap-based system allocation, + removing need for emulation on most platforms without sbrk. + * Default safety checks + * Optional footer checks. Thanks to William Robertson for the idea. + * Internal code refactoring + * Incorporate suggestions and platform-specific changes. + Thanks to Dennis Flanagan, Colin Plumb, Niall Douglas, + Aaron Bachmann, Emery Berger, and others. + * Speed up non-fastbin processing enough to remove fastbins. + * Remove useless cfree() to avoid conflicts with other apps. + * Remove internal memcpy, memset. Compilers handle builtins better. + * Remove some options that no one ever used and rename others. + + V2.7.2 Sat Aug 17 09:07:30 2002 Doug Lea (dl at gee) + * Fix malloc_state bitmap array misdeclaration + + V2.7.1 Thu Jul 25 10:58:03 2002 Doug Lea (dl at gee) + * Allow tuning of FIRST_SORTED_BIN_SIZE + * Use PTR_UINT as type for all ptr->int casts. Thanks to John Belmonte. + * Better detection and support for non-contiguousness of MORECORE. + Thanks to Andreas Mueller, Conal Walsh, and Wolfram Gloger + * Bypass most of malloc if no frees. Thanks To Emery Berger. + * Fix freeing of old top non-contiguous chunk im sysmalloc. + * Raised default trim and map thresholds to 256K. + * Fix mmap-related #defines. Thanks to Lubos Lunak. + * Fix copy macros; added LACKS_FCNTL_H. Thanks to Neal Walfield. + * Branch-free bin calculation + * Default trim and mmap thresholds now 256K. + + V2.7.0 Sun Mar 11 14:14:06 2001 Doug Lea (dl at gee) + * Introduce independent_comalloc and independent_calloc. + Thanks to Michael Pachos for motivation and help. + * Make optional .h file available + * Allow > 2GB requests on 32bit systems. + * new WIN32 sbrk, mmap, munmap, lock code from <Walter@GeNeSys-e.de>. + Thanks also to Andreas Mueller <a.mueller at paradatec.de>, + and Anonymous. + * Allow override of MALLOC_ALIGNMENT (Thanks to Ruud Waij for + helping test this.) + * memalign: check alignment arg + * realloc: don't try to shift chunks backwards, since this + leads to more fragmentation in some programs and doesn't + seem to help in any others. + * Collect all cases in malloc requiring system memory into sysmalloc + * Use mmap as backup to sbrk + * Place all internal state in malloc_state + * Introduce fastbins (although similar to 2.5.1) + * Many minor tunings and cosmetic improvements + * Introduce USE_PUBLIC_MALLOC_WRAPPERS, USE_MALLOC_LOCK + * Introduce MALLOC_FAILURE_ACTION, MORECORE_CONTIGUOUS + Thanks to Tony E. Bennett <tbennett@nvidia.com> and others. + * Include errno.h to support default failure action. + + V2.6.6 Sun Dec 5 07:42:19 1999 Doug Lea (dl at gee) + * return null for negative arguments + * Added Several WIN32 cleanups from Martin C. Fong <mcfong at yahoo.com> + * Add 'LACKS_SYS_PARAM_H' for those systems without 'sys/param.h' + (e.g. WIN32 platforms) + * Cleanup header file inclusion for WIN32 platforms + * Cleanup code to avoid Microsoft Visual C++ compiler complaints + * Add 'USE_DL_PREFIX' to quickly allow co-existence with existing + memory allocation routines + * Set 'malloc_getpagesize' for WIN32 platforms (needs more work) + * Use 'assert' rather than 'ASSERT' in WIN32 code to conform to + usage of 'assert' in non-WIN32 code + * Improve WIN32 'sbrk()' emulation's 'findRegion()' routine to + avoid infinite loop + * Always call 'fREe()' rather than 'free()' + + V2.6.5 Wed Jun 17 15:57:31 1998 Doug Lea (dl at gee) + * Fixed ordering problem with boundary-stamping + + V2.6.3 Sun May 19 08:17:58 1996 Doug Lea (dl at gee) + * Added pvalloc, as recommended by H.J. Liu + * Added 64bit pointer support mainly from Wolfram Gloger + * Added anonymously donated WIN32 sbrk emulation + * Malloc, calloc, getpagesize: add optimizations from Raymond Nijssen + * malloc_extend_top: fix mask error that caused wastage after + foreign sbrks + * Add linux mremap support code from HJ Liu + + V2.6.2 Tue Dec 5 06:52:55 1995 Doug Lea (dl at gee) + * Integrated most documentation with the code. + * Add support for mmap, with help from + Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Use last_remainder in more cases. + * Pack bins using idea from colin@nyx10.cs.du.edu + * Use ordered bins instead of best-fit threshhold + * Eliminate block-local decls to simplify tracing and debugging. + * Support another case of realloc via move into top + * Fix error occuring when initial sbrk_base not word-aligned. + * Rely on page size for units instead of SBRK_UNIT to + avoid surprises about sbrk alignment conventions. + * Add mallinfo, mallopt. Thanks to Raymond Nijssen + (raymond@es.ele.tue.nl) for the suggestion. + * Add `pad' argument to malloc_trim and top_pad mallopt parameter. + * More precautions for cases where other routines call sbrk, + courtesy of Wolfram Gloger (Gloger@lrz.uni-muenchen.de). + * Added macros etc., allowing use in linux libc from + H.J. Lu (hjl@gnu.ai.mit.edu) + * Inverted this history list + + V2.6.1 Sat Dec 2 14:10:57 1995 Doug Lea (dl at gee) + * Re-tuned and fixed to behave more nicely with V2.6.0 changes. + * Removed all preallocation code since under current scheme + the work required to undo bad preallocations exceeds + the work saved in good cases for most test programs. + * No longer use return list or unconsolidated bins since + no scheme using them consistently outperforms those that don't + given above changes. + * Use best fit for very large chunks to prevent some worst-cases. + * Added some support for debugging + + V2.6.0 Sat Nov 4 07:05:23 1995 Doug Lea (dl at gee) + * Removed footers when chunks are in use. Thanks to + Paul Wilson (wilson@cs.texas.edu) for the suggestion. + + V2.5.4 Wed Nov 1 07:54:51 1995 Doug Lea (dl at gee) + * Added malloc_trim, with help from Wolfram Gloger + (wmglo@Dent.MED.Uni-Muenchen.DE). + + V2.5.3 Tue Apr 26 10:16:01 1994 Doug Lea (dl at g) + + V2.5.2 Tue Apr 5 16:20:40 1994 Doug Lea (dl at g) + * realloc: try to expand in both directions + * malloc: swap order of clean-bin strategy; + * realloc: only conditionally expand backwards + * Try not to scavenge used bins + * Use bin counts as a guide to preallocation + * Occasionally bin return list chunks in first scan + * Add a few optimizations from colin@nyx10.cs.du.edu + + V2.5.1 Sat Aug 14 15:40:43 1993 Doug Lea (dl at g) + * faster bin computation & slightly different binning + * merged all consolidations to one part of malloc proper + (eliminating old malloc_find_space & malloc_clean_bin) + * Scan 2 returns chunks (not just 1) + * Propagate failure in realloc if malloc returns 0 + * Add stuff to allow compilation on non-ANSI compilers + from kpv@research.att.com + + V2.5 Sat Aug 7 07:41:59 1993 Doug Lea (dl at g.oswego.edu) + * removed potential for odd address access in prev_chunk + * removed dependency on getpagesize.h + * misc cosmetics and a bit more internal documentation + * anticosmetics: mangled names in macros to evade debugger strangeness + * tested on sparc, hp-700, dec-mips, rs6000 + with gcc & native cc (hp, dec only) allowing + Detlefs & Zorn comparison study (in SIGPLAN Notices.) + + Trial version Fri Aug 28 13:14:29 1992 Doug Lea (dl at g.oswego.edu) + * Based loosely on libg++-1.2X malloc. (It retains some of the overall + structure of old version, but most details differ.) + +*/ + + diff --git a/compat/nedmalloc/nedmalloc.c b/compat/nedmalloc/nedmalloc.c new file mode 100644 index 0000000000..d9a17a8057 --- /dev/null +++ b/compat/nedmalloc/nedmalloc.c @@ -0,0 +1,966 @@ +/* Alternative malloc implementation for multiple threads without +lock contention based on dlmalloc. (C) 2005-2006 Niall Douglas + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifdef _MSC_VER +/* Enable full aliasing on MSVC */ +/*#pragma optimize("a", on)*/ +#endif + +/*#define FULLSANITYCHECKS*/ + +#include "nedmalloc.h" +#if defined(WIN32) + #include <malloc.h> +#endif +#define MSPACES 1 +#define ONLY_MSPACES 1 +#ifndef USE_LOCKS + #define USE_LOCKS 1 +#endif +#define FOOTERS 1 /* Need to enable footers so frees lock the right mspace */ +#undef DEBUG /* dlmalloc wants DEBUG either 0 or 1 */ +#ifdef _DEBUG + #define DEBUG 1 +#else + #define DEBUG 0 +#endif +#ifdef NDEBUG /* Disable assert checking on release builds */ + #undef DEBUG +#endif +/* The default of 64Kb means we spend too much time kernel-side */ +#ifndef DEFAULT_GRANULARITY +#define DEFAULT_GRANULARITY (1*1024*1024) +#endif +/*#define USE_SPIN_LOCKS 0*/ + + +/*#define FORCEINLINE*/ +#include "malloc.c.h" +#ifdef NDEBUG /* Disable assert checking on release builds */ + #undef DEBUG +#endif + +/* The maximum concurrent threads in a pool possible */ +#ifndef MAXTHREADSINPOOL +#define MAXTHREADSINPOOL 16 +#endif +/* The maximum number of threadcaches which can be allocated */ +#ifndef THREADCACHEMAXCACHES +#define THREADCACHEMAXCACHES 256 +#endif +/* The maximum size to be allocated from the thread cache */ +#ifndef THREADCACHEMAX +#define THREADCACHEMAX 8192 +#endif +#if 0 +/* The number of cache entries for finer grained bins. This is (topbitpos(THREADCACHEMAX)-4)*2 */ +#define THREADCACHEMAXBINS ((13-4)*2) +#else +/* The number of cache entries. This is (topbitpos(THREADCACHEMAX)-4) */ +#define THREADCACHEMAXBINS (13-4) +#endif +/* Point at which the free space in a thread cache is garbage collected */ +#ifndef THREADCACHEMAXFREESPACE +#define THREADCACHEMAXFREESPACE (512*1024) +#endif + + +#ifdef WIN32 + #define TLSVAR DWORD + #define TLSALLOC(k) (*(k)=TlsAlloc(), TLS_OUT_OF_INDEXES==*(k)) + #define TLSFREE(k) (!TlsFree(k)) + #define TLSGET(k) TlsGetValue(k) + #define TLSSET(k, a) (!TlsSetValue(k, a)) + #ifdef DEBUG +static LPVOID ChkedTlsGetValue(DWORD idx) +{ + LPVOID ret=TlsGetValue(idx); + assert(S_OK==GetLastError()); + return ret; +} + #undef TLSGET + #define TLSGET(k) ChkedTlsGetValue(k) + #endif +#else + #define TLSVAR pthread_key_t + #define TLSALLOC(k) pthread_key_create(k, 0) + #define TLSFREE(k) pthread_key_delete(k) + #define TLSGET(k) pthread_getspecific(k) + #define TLSSET(k, a) pthread_setspecific(k, a) +#endif + +#if 0 +/* Only enable if testing with valgrind. Causes misoperation */ +#define mspace_malloc(p, s) malloc(s) +#define mspace_realloc(p, m, s) realloc(m, s) +#define mspace_calloc(p, n, s) calloc(n, s) +#define mspace_free(p, m) free(m) +#endif + + +#if defined(__cplusplus) +#if !defined(NO_NED_NAMESPACE) +namespace nedalloc { +#else +extern "C" { +#endif +#endif + +size_t nedblksize(void *mem) THROWSPEC +{ +#if 0 + /* Only enable if testing with valgrind. Causes misoperation */ + return THREADCACHEMAX; +#else + if(mem) + { + mchunkptr p=mem2chunk(mem); + assert(cinuse(p)); /* If this fails, someone tried to free a block twice */ + if(cinuse(p)) + return chunksize(p)-overhead_for(p); + } + return 0; +#endif +} + +void nedsetvalue(void *v) THROWSPEC { nedpsetvalue(0, v); } +void * nedmalloc(size_t size) THROWSPEC { return nedpmalloc(0, size); } +void * nedcalloc(size_t no, size_t size) THROWSPEC { return nedpcalloc(0, no, size); } +void * nedrealloc(void *mem, size_t size) THROWSPEC { return nedprealloc(0, mem, size); } +void nedfree(void *mem) THROWSPEC { nedpfree(0, mem); } +void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC { return nedpmemalign(0, alignment, bytes); } +#if !NO_MALLINFO +struct mallinfo nedmallinfo(void) THROWSPEC { return nedpmallinfo(0); } +#endif +int nedmallopt(int parno, int value) THROWSPEC { return nedpmallopt(0, parno, value); } +int nedmalloc_trim(size_t pad) THROWSPEC { return nedpmalloc_trim(0, pad); } +void nedmalloc_stats() THROWSPEC { nedpmalloc_stats(0); } +size_t nedmalloc_footprint() THROWSPEC { return nedpmalloc_footprint(0); } +void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC { return nedpindependent_calloc(0, elemsno, elemsize, chunks); } +void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC { return nedpindependent_comalloc(0, elems, sizes, chunks); } + +struct threadcacheblk_t; +typedef struct threadcacheblk_t threadcacheblk; +struct threadcacheblk_t +{ /* Keep less than 16 bytes on 32 bit systems and 32 bytes on 64 bit systems */ +#ifdef FULLSANITYCHECKS + unsigned int magic; +#endif + unsigned int lastUsed, size; + threadcacheblk *next, *prev; +}; +typedef struct threadcache_t +{ +#ifdef FULLSANITYCHECKS + unsigned int magic1; +#endif + int mymspace; /* Last mspace entry this thread used */ + long threadid; + unsigned int mallocs, frees, successes; + size_t freeInCache; /* How much free space is stored in this cache */ + threadcacheblk *bins[(THREADCACHEMAXBINS+1)*2]; +#ifdef FULLSANITYCHECKS + unsigned int magic2; +#endif +} threadcache; +struct nedpool_t +{ + MLOCK_T mutex; + void *uservalue; + int threads; /* Max entries in m to use */ + threadcache *caches[THREADCACHEMAXCACHES]; + TLSVAR mycache; /* Thread cache for this thread. 0 for unset, negative for use mspace-1 directly, otherwise is cache-1 */ + mstate m[MAXTHREADSINPOOL+1]; /* mspace entries for this pool */ +}; +static nedpool syspool; + +static FORCEINLINE unsigned int size2binidx(size_t _size) THROWSPEC +{ /* 8=1000 16=10000 20=10100 24=11000 32=100000 48=110000 4096=1000000000000 */ + unsigned int topbit, size=(unsigned int)(_size>>4); + /* 16=1 20=1 24=1 32=10 48=11 64=100 96=110 128=1000 4096=100000000 */ + +#if defined(__GNUC__) + topbit = sizeof(size)*__CHAR_BIT__ - 1 - __builtin_clz(size); +#elif defined(_MSC_VER) && _MSC_VER>=1300 + { + unsigned long bsrTopBit; + + _BitScanReverse(&bsrTopBit, size); + + topbit = bsrTopBit; + } +#else +#if 0 + union { + unsigned asInt[2]; + double asDouble; + }; + int n; + + asDouble = (double)size + 0.5; + topbit = (asInt[!FOX_BIGENDIAN] >> 20) - 1023; +#else + { + unsigned int x=size; + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >>16); + x = ~x; + x = x - ((x >> 1) & 0x55555555); + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x = x + (x << 8); + x = x + (x << 16); + topbit=31 - (x >> 24); + } +#endif +#endif + return topbit; +} + + +#ifdef FULLSANITYCHECKS +static void tcsanitycheck(threadcacheblk **ptr) THROWSPEC +{ + assert((ptr[0] && ptr[1]) || (!ptr[0] && !ptr[1])); + if(ptr[0] && ptr[1]) + { + assert(nedblksize(ptr[0])>=sizeof(threadcacheblk)); + assert(nedblksize(ptr[1])>=sizeof(threadcacheblk)); + assert(*(unsigned int *) "NEDN"==ptr[0]->magic); + assert(*(unsigned int *) "NEDN"==ptr[1]->magic); + assert(!ptr[0]->prev); + assert(!ptr[1]->next); + if(ptr[0]==ptr[1]) + { + assert(!ptr[0]->next); + assert(!ptr[1]->prev); + } + } +} +static void tcfullsanitycheck(threadcache *tc) THROWSPEC +{ + threadcacheblk **tcbptr=tc->bins; + int n; + for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2) + { + threadcacheblk *b, *ob=0; + tcsanitycheck(tcbptr); + for(b=tcbptr[0]; b; ob=b, b=b->next) + { + assert(*(unsigned int *) "NEDN"==b->magic); + assert(!ob || ob->next==b); + assert(!ob || b->prev==ob); + } + } +} +#endif + +static NOINLINE void RemoveCacheEntries(nedpool *p, threadcache *tc, unsigned int age) THROWSPEC +{ +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + if(tc->freeInCache) + { + threadcacheblk **tcbptr=tc->bins; + int n; + for(n=0; n<=THREADCACHEMAXBINS; n++, tcbptr+=2) + { + threadcacheblk **tcb=tcbptr+1; /* come from oldest end of list */ + /*tcsanitycheck(tcbptr);*/ + for(; *tcb && tc->frees-(*tcb)->lastUsed>=age; ) + { + threadcacheblk *f=*tcb; + size_t blksize=f->size; /*nedblksize(f);*/ + assert(blksize<=nedblksize(f)); + assert(blksize); +#ifdef FULLSANITYCHECKS + assert(*(unsigned int *) "NEDN"==(*tcb)->magic); +#endif + *tcb=(*tcb)->prev; + if(*tcb) + (*tcb)->next=0; + else + *tcbptr=0; + tc->freeInCache-=blksize; + assert((long) tc->freeInCache>=0); + mspace_free(0, f); + /*tcsanitycheck(tcbptr);*/ + } + } + } +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif +} +static void DestroyCaches(nedpool *p) THROWSPEC +{ + if(p->caches) + { + threadcache *tc; + int n; + for(n=0; n<THREADCACHEMAXCACHES; n++) + { + if((tc=p->caches[n])) + { + tc->frees++; + RemoveCacheEntries(p, tc, 0); + assert(!tc->freeInCache); + tc->mymspace=-1; + tc->threadid=0; + mspace_free(0, tc); + p->caches[n]=0; + } + } + } +} + +static NOINLINE threadcache *AllocCache(nedpool *p) THROWSPEC +{ + threadcache *tc=0; + int n, end; + ACQUIRE_LOCK(&p->mutex); + for(n=0; n<THREADCACHEMAXCACHES && p->caches[n]; n++); + if(THREADCACHEMAXCACHES==n) + { /* List exhausted, so disable for this thread */ + RELEASE_LOCK(&p->mutex); + return 0; + } + tc=p->caches[n]=(threadcache *) mspace_calloc(p->m[0], 1, sizeof(threadcache)); + if(!tc) + { + RELEASE_LOCK(&p->mutex); + return 0; + } +#ifdef FULLSANITYCHECKS + tc->magic1=*(unsigned int *)"NEDMALC1"; + tc->magic2=*(unsigned int *)"NEDMALC2"; +#endif + tc->threadid=(long)(size_t)CURRENT_THREAD; + for(end=0; p->m[end]; end++); + tc->mymspace=tc->threadid % end; + RELEASE_LOCK(&p->mutex); + if(TLSSET(p->mycache, (void *)(size_t)(n+1))) abort(); + return tc; +} + +static void *threadcache_malloc(nedpool *p, threadcache *tc, size_t *size) THROWSPEC +{ + void *ret=0; + unsigned int bestsize; + unsigned int idx=size2binidx(*size); + size_t blksize=0; + threadcacheblk *blk, **binsptr; +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + /* Calculate best fit bin size */ + bestsize=1<<(idx+4); +#if 0 + /* Finer grained bin fit */ + idx<<=1; + if(*size>bestsize) + { + idx++; + bestsize+=bestsize>>1; + } + if(*size>bestsize) + { + idx++; + bestsize=1<<(4+(idx>>1)); + } +#else + if(*size>bestsize) + { + idx++; + bestsize<<=1; + } +#endif + assert(bestsize>=*size); + if(*size<bestsize) *size=bestsize; + assert(*size<=THREADCACHEMAX); + assert(idx<=THREADCACHEMAXBINS); + binsptr=&tc->bins[idx*2]; + /* Try to match close, but move up a bin if necessary */ + blk=*binsptr; + if(!blk || blk->size<*size) + { /* Bump it up a bin */ + if(idx<THREADCACHEMAXBINS) + { + idx++; + binsptr+=2; + blk=*binsptr; + } + } + if(blk) + { + blksize=blk->size; /*nedblksize(blk);*/ + assert(nedblksize(blk)>=blksize); + assert(blksize>=*size); + if(blk->next) + blk->next->prev=0; + *binsptr=blk->next; + if(!*binsptr) + binsptr[1]=0; +#ifdef FULLSANITYCHECKS + blk->magic=0; +#endif + assert(binsptr[0]!=blk && binsptr[1]!=blk); + assert(nedblksize(blk)>=sizeof(threadcacheblk) && nedblksize(blk)<=THREADCACHEMAX+CHUNK_OVERHEAD); + /*printf("malloc: %p, %p, %p, %lu\n", p, tc, blk, (long) size);*/ + ret=(void *) blk; + } + ++tc->mallocs; + if(ret) + { + assert(blksize>=*size); + ++tc->successes; + tc->freeInCache-=blksize; + assert((long) tc->freeInCache>=0); + } +#if defined(DEBUG) && 0 + if(!(tc->mallocs & 0xfff)) + { + printf("*** threadcache=%u, mallocs=%u (%f), free=%u (%f), freeInCache=%u\n", (unsigned int) tc->threadid, tc->mallocs, + (float) tc->successes/tc->mallocs, tc->frees, (float) tc->successes/tc->frees, (unsigned int) tc->freeInCache); + } +#endif +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + return ret; +} +static NOINLINE void ReleaseFreeInCache(nedpool *p, threadcache *tc, int mymspace) THROWSPEC +{ + unsigned int age=THREADCACHEMAXFREESPACE/8192; + /*ACQUIRE_LOCK(&p->m[mymspace]->mutex);*/ + while(age && tc->freeInCache>=THREADCACHEMAXFREESPACE) + { + RemoveCacheEntries(p, tc, age); + /*printf("*** Removing cache entries older than %u (%u)\n", age, (unsigned int) tc->freeInCache);*/ + age>>=1; + } + /*RELEASE_LOCK(&p->m[mymspace]->mutex);*/ +} +static void threadcache_free(nedpool *p, threadcache *tc, int mymspace, void *mem, size_t size) THROWSPEC +{ + unsigned int bestsize; + unsigned int idx=size2binidx(size); + threadcacheblk **binsptr, *tck=(threadcacheblk *) mem; + assert(size>=sizeof(threadcacheblk) && size<=THREADCACHEMAX+CHUNK_OVERHEAD); +#ifdef DEBUG + { /* Make sure this is a valid memory block */ + mchunkptr p = mem2chunk(mem); + mstate fm = get_mstate_for(p); + if (!ok_magic(fm)) { + USAGE_ERROR_ACTION(fm, p); + return; + } + } +#endif +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif + /* Calculate best fit bin size */ + bestsize=1<<(idx+4); +#if 0 + /* Finer grained bin fit */ + idx<<=1; + if(size>bestsize) + { + unsigned int biggerbestsize=bestsize+bestsize<<1; + if(size>=biggerbestsize) + { + idx++; + bestsize=biggerbestsize; + } + } +#endif + if(bestsize!=size) /* dlmalloc can round up, so we round down to preserve indexing */ + size=bestsize; + binsptr=&tc->bins[idx*2]; + assert(idx<=THREADCACHEMAXBINS); + if(tck==*binsptr) + { + fprintf(stderr, "Attempt to free already freed memory block %p - aborting!\n", tck); + abort(); + } +#ifdef FULLSANITYCHECKS + tck->magic=*(unsigned int *) "NEDN"; +#endif + tck->lastUsed=++tc->frees; + tck->size=(unsigned int) size; + tck->next=*binsptr; + tck->prev=0; + if(tck->next) + tck->next->prev=tck; + else + binsptr[1]=tck; + assert(!*binsptr || (*binsptr)->size==tck->size); + *binsptr=tck; + assert(tck==tc->bins[idx*2]); + assert(tc->bins[idx*2+1]==tck || binsptr[0]->next->prev==tck); + /*printf("free: %p, %p, %p, %lu\n", p, tc, mem, (long) size);*/ + tc->freeInCache+=size; +#ifdef FULLSANITYCHECKS + tcfullsanitycheck(tc); +#endif +#if 1 + if(tc->freeInCache>=THREADCACHEMAXFREESPACE) + ReleaseFreeInCache(p, tc, mymspace); +#endif +} + + + + +static NOINLINE int InitPool(nedpool *p, size_t capacity, int threads) THROWSPEC +{ /* threads is -1 for system pool */ + ensure_initialization(); + ACQUIRE_MALLOC_GLOBAL_LOCK(); + if(p->threads) goto done; + if(INITIAL_LOCK(&p->mutex)) goto err; + if(TLSALLOC(&p->mycache)) goto err; + if(!(p->m[0]=(mstate) create_mspace(capacity, 1))) goto err; + p->m[0]->extp=p; + p->threads=(threads<1 || threads>MAXTHREADSINPOOL) ? MAXTHREADSINPOOL : threads; +done: + RELEASE_MALLOC_GLOBAL_LOCK(); + return 1; +err: + if(threads<0) + abort(); /* If you can't allocate for system pool, we're screwed */ + DestroyCaches(p); + if(p->m[0]) + { + destroy_mspace(p->m[0]); + p->m[0]=0; + } + if(p->mycache) + { + if(TLSFREE(p->mycache)) abort(); + p->mycache=0; + } + RELEASE_MALLOC_GLOBAL_LOCK(); + return 0; +} +static NOINLINE mstate FindMSpace(nedpool *p, threadcache *tc, int *lastUsed, size_t size) THROWSPEC +{ /* Gets called when thread's last used mspace is in use. The strategy + is to run through the list of all available mspaces looking for an + unlocked one and if we fail, we create a new one so long as we don't + exceed p->threads */ + int n, end; + for(n=end=*lastUsed+1; p->m[n]; end=++n) + { + if(TRY_LOCK(&p->m[n]->mutex)) goto found; + } + for(n=0; n<*lastUsed && p->m[n]; n++) + { + if(TRY_LOCK(&p->m[n]->mutex)) goto found; + } + if(end<p->threads) + { + mstate temp; + if(!(temp=(mstate) create_mspace(size, 1))) + goto badexit; + /* Now we're ready to modify the lists, we lock */ + ACQUIRE_LOCK(&p->mutex); + while(p->m[end] && end<p->threads) + end++; + if(end>=p->threads) + { /* Drat, must destroy it now */ + RELEASE_LOCK(&p->mutex); + destroy_mspace((mspace) temp); + goto badexit; + } + /* We really want to make sure this goes into memory now but we + have to be careful of breaking aliasing rules, so write it twice */ + *((volatile struct malloc_state **) &p->m[end])=p->m[end]=temp; + ACQUIRE_LOCK(&p->m[end]->mutex); + /*printf("Created mspace idx %d\n", end);*/ + RELEASE_LOCK(&p->mutex); + n=end; + goto found; + } + /* Let it lock on the last one it used */ +badexit: + ACQUIRE_LOCK(&p->m[*lastUsed]->mutex); + return p->m[*lastUsed]; +found: + *lastUsed=n; + if(tc) + tc->mymspace=n; + else + { + if(TLSSET(p->mycache, (void *)(size_t)(-(n+1)))) abort(); + } + return p->m[n]; +} + +nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC +{ + nedpool *ret; + if(!(ret=(nedpool *) nedpcalloc(0, 1, sizeof(nedpool)))) return 0; + if(!InitPool(ret, capacity, threads)) + { + nedpfree(0, ret); + return 0; + } + return ret; +} +void neddestroypool(nedpool *p) THROWSPEC +{ + int n; + ACQUIRE_LOCK(&p->mutex); + DestroyCaches(p); + for(n=0; p->m[n]; n++) + { + destroy_mspace(p->m[n]); + p->m[n]=0; + } + RELEASE_LOCK(&p->mutex); + if(TLSFREE(p->mycache)) abort(); + nedpfree(0, p); +} + +void nedpsetvalue(nedpool *p, void *v) THROWSPEC +{ + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + p->uservalue=v; +} +void *nedgetvalue(nedpool **p, void *mem) THROWSPEC +{ + nedpool *np=0; + mchunkptr mcp=mem2chunk(mem); + mstate fm; + if(!(is_aligned(chunk2mem(mcp))) && mcp->head != FENCEPOST_HEAD) return 0; + if(!cinuse(mcp)) return 0; + if(!next_pinuse(mcp)) return 0; + if(!is_mmapped(mcp) && !pinuse(mcp)) + { + if(next_chunk(prev_chunk(mcp))!=mcp) return 0; + } + fm=get_mstate_for(mcp); + if(!ok_magic(fm)) return 0; + if(!ok_address(fm, mcp)) return 0; + if(!fm->extp) return 0; + np=(nedpool *) fm->extp; + if(p) *p=np; + return np->uservalue; +} + +void neddisablethreadcache(nedpool *p) THROWSPEC +{ + int mycache; + if(!p) + { + p=&syspool; + if(!syspool.threads) InitPool(&syspool, 0, -1); + } + mycache=(int)(size_t) TLSGET(p->mycache); + if(!mycache) + { /* Set to mspace 0 */ + if(TLSSET(p->mycache, (void *)-1)) abort(); + } + else if(mycache>0) + { /* Set to last used mspace */ + threadcache *tc=p->caches[mycache-1]; +#if defined(DEBUG) + printf("Threadcache utilisation: %lf%% in cache with %lf%% lost to other threads\n", + 100.0*tc->successes/tc->mallocs, 100.0*((double) tc->mallocs-tc->frees)/tc->mallocs); +#endif + if(TLSSET(p->mycache, (void *)(size_t)(-tc->mymspace))) abort(); + tc->frees++; + RemoveCacheEntries(p, tc, 0); + assert(!tc->freeInCache); + tc->mymspace=-1; + tc->threadid=0; + mspace_free(0, p->caches[mycache-1]); + p->caches[mycache-1]=0; + } +} + +#define GETMSPACE(m,p,tc,ms,s,action) \ + do \ + { \ + mstate m = GetMSpace((p),(tc),(ms),(s)); \ + action; \ + RELEASE_LOCK(&m->mutex); \ + } while (0) + +static FORCEINLINE mstate GetMSpace(nedpool *p, threadcache *tc, int mymspace, size_t size) THROWSPEC +{ /* Returns a locked and ready for use mspace */ + mstate m=p->m[mymspace]; + assert(m); + if(!TRY_LOCK(&p->m[mymspace]->mutex)) m=FindMSpace(p, tc, &mymspace, size);\ + /*assert(IS_LOCKED(&p->m[mymspace]->mutex));*/ + return m; +} +static FORCEINLINE void GetThreadCache(nedpool **p, threadcache **tc, int *mymspace, size_t *size) THROWSPEC +{ + int mycache; + if(size && *size<sizeof(threadcacheblk)) *size=sizeof(threadcacheblk); + if(!*p) + { + *p=&syspool; + if(!syspool.threads) InitPool(&syspool, 0, -1); + } + mycache=(int)(size_t) TLSGET((*p)->mycache); + if(mycache>0) + { + *tc=(*p)->caches[mycache-1]; + *mymspace=(*tc)->mymspace; + } + else if(!mycache) + { + *tc=AllocCache(*p); + if(!*tc) + { /* Disable */ + if(TLSSET((*p)->mycache, (void *)-1)) abort(); + *mymspace=0; + } + else + *mymspace=(*tc)->mymspace; + } + else + { + *tc=0; + *mymspace=-mycache-1; + } + assert(*mymspace>=0); + assert((long)(size_t)CURRENT_THREAD==(*tc)->threadid); +#ifdef FULLSANITYCHECKS + if(*tc) + { + if(*(unsigned int *)"NEDMALC1"!=(*tc)->magic1 || *(unsigned int *)"NEDMALC2"!=(*tc)->magic2) + { + abort(); + } + } +#endif +} + +void * nedpmalloc(nedpool *p, size_t size) THROWSPEC +{ + void *ret=0; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &size); +#if THREADCACHEMAX + if(tc && size<=THREADCACHEMAX) + { /* Use the thread cache */ + ret=threadcache_malloc(p, tc, &size); + } +#endif + if(!ret) + { /* Use this thread's mspace */ + GETMSPACE(m, p, tc, mymspace, size, + ret=mspace_malloc(m, size)); + } + return ret; +} +void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC +{ + size_t rsize=size*no; + void *ret=0; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &rsize); +#if THREADCACHEMAX + if(tc && rsize<=THREADCACHEMAX) + { /* Use the thread cache */ + if((ret=threadcache_malloc(p, tc, &rsize))) + memset(ret, 0, rsize); + } +#endif + if(!ret) + { /* Use this thread's mspace */ + GETMSPACE(m, p, tc, mymspace, rsize, + ret=mspace_calloc(m, 1, rsize)); + } + return ret; +} +void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC +{ + void *ret=0; + threadcache *tc; + int mymspace; + if(!mem) return nedpmalloc(p, size); + GetThreadCache(&p, &tc, &mymspace, &size); +#if THREADCACHEMAX + if(tc && size && size<=THREADCACHEMAX) + { /* Use the thread cache */ + size_t memsize=nedblksize(mem); + assert(memsize); + if((ret=threadcache_malloc(p, tc, &size))) + { + memcpy(ret, mem, memsize<size ? memsize : size); + if(memsize<=THREADCACHEMAX) + threadcache_free(p, tc, mymspace, mem, memsize); + else + mspace_free(0, mem); + } + } +#endif + if(!ret) + { /* Reallocs always happen in the mspace they happened in, so skip + locking the preferred mspace for this thread */ + ret=mspace_realloc(0, mem, size); + } + return ret; +} +void nedpfree(nedpool *p, void *mem) THROWSPEC +{ /* Frees always happen in the mspace they happened in, so skip + locking the preferred mspace for this thread */ + threadcache *tc; + int mymspace; + size_t memsize; + assert(mem); + GetThreadCache(&p, &tc, &mymspace, 0); +#if THREADCACHEMAX + memsize=nedblksize(mem); + assert(memsize); + if(mem && tc && memsize<=(THREADCACHEMAX+CHUNK_OVERHEAD)) + threadcache_free(p, tc, mymspace, mem, memsize); + else +#endif + mspace_free(0, mem); +} +void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC +{ + void *ret; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &bytes); + { /* Use this thread's mspace */ + GETMSPACE(m, p, tc, mymspace, bytes, + ret=mspace_memalign(m, alignment, bytes)); + } + return ret; +} +#if !NO_MALLINFO +struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC +{ + int n; + struct mallinfo ret={0}; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + struct mallinfo t=mspace_mallinfo(p->m[n]); + ret.arena+=t.arena; + ret.ordblks+=t.ordblks; + ret.hblkhd+=t.hblkhd; + ret.usmblks+=t.usmblks; + ret.uordblks+=t.uordblks; + ret.fordblks+=t.fordblks; + ret.keepcost+=t.keepcost; + } + return ret; +} +#endif +int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC +{ + return mspace_mallopt(parno, value); +} +int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC +{ + int n, ret=0; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + ret+=mspace_trim(p->m[n], pad); + } + return ret; +} +void nedpmalloc_stats(nedpool *p) THROWSPEC +{ + int n; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + mspace_malloc_stats(p->m[n]); + } +} +size_t nedpmalloc_footprint(nedpool *p) THROWSPEC +{ + size_t ret=0; + int n; + if(!p) { p=&syspool; if(!syspool.threads) InitPool(&syspool, 0, -1); } + for(n=0; p->m[n]; n++) + { + ret+=mspace_footprint(p->m[n]); + } + return ret; +} +void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC +{ + void **ret; + threadcache *tc; + int mymspace; + GetThreadCache(&p, &tc, &mymspace, &elemsize); + GETMSPACE(m, p, tc, mymspace, elemsno*elemsize, + ret=mspace_independent_calloc(m, elemsno, elemsize, chunks)); + return ret; +} +void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC +{ + void **ret; + threadcache *tc; + int mymspace; + size_t i, *adjustedsizes=(size_t *) alloca(elems*sizeof(size_t)); + if(!adjustedsizes) return 0; + for(i=0; i<elems; i++) + adjustedsizes[i]=sizes[i]<sizeof(threadcacheblk) ? sizeof(threadcacheblk) : sizes[i]; + GetThreadCache(&p, &tc, &mymspace, 0); + GETMSPACE(m, p, tc, mymspace, 0, + ret=mspace_independent_comalloc(m, elems, adjustedsizes, chunks)); + return ret; +} + +#ifdef OVERRIDE_STRDUP +/* + * This implementation is purely there to override the libc version, to + * avoid a crash due to allocation and free on different 'heaps'. + */ +char *strdup(const char *s1) +{ + char *s2 = 0; + if (s1) { + s2 = malloc(strlen(s1) + 1); + strcpy(s2, s1); + } + return s2; +} +#endif + +#if defined(__cplusplus) +} +#endif diff --git a/compat/nedmalloc/nedmalloc.h b/compat/nedmalloc/nedmalloc.h new file mode 100644 index 0000000000..f960e66063 --- /dev/null +++ b/compat/nedmalloc/nedmalloc.h @@ -0,0 +1,180 @@ +/* nedalloc, an alternative malloc implementation for multiple threads without +lock contention based on dlmalloc v2.8.3. (C) 2005 Niall Douglas + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef NEDMALLOC_H +#define NEDMALLOC_H + + +/* See malloc.c.h for what each function does. + +REPLACE_SYSTEM_ALLOCATOR causes nedalloc's functions to be called malloc, +free etc. instead of nedmalloc, nedfree etc. You may or may not want this. + +NO_NED_NAMESPACE prevents the functions from being defined in the nedalloc +namespace when in C++ (uses the global namespace instead). + +EXTSPEC can be defined to be __declspec(dllexport) or +__attribute__ ((visibility("default"))) or whatever you like. It defaults +to extern. + +USE_LOCKS can be 2 if you want to define your own MLOCK_T, INITIAL_LOCK, +ACQUIRE_LOCK, RELEASE_LOCK, TRY_LOCK, IS_LOCKED and NULL_LOCK_INITIALIZER. + +*/ + +#include <stddef.h> /* for size_t */ + +#ifndef EXTSPEC + #define EXTSPEC extern +#endif + +#if defined(_MSC_VER) && _MSC_VER>=1400 + #define MALLOCATTR __declspec(restrict) +#endif +#ifdef __GNUC__ + #define MALLOCATTR __attribute__ ((malloc)) +#endif +#ifndef MALLOCATTR + #define MALLOCATTR +#endif + +#ifdef REPLACE_SYSTEM_ALLOCATOR + #define nedmalloc malloc + #define nedcalloc calloc + #define nedrealloc realloc + #define nedfree free + #define nedmemalign memalign + #define nedmallinfo mallinfo + #define nedmallopt mallopt + #define nedmalloc_trim malloc_trim + #define nedmalloc_stats malloc_stats + #define nedmalloc_footprint malloc_footprint + #define nedindependent_calloc independent_calloc + #define nedindependent_comalloc independent_comalloc + #ifdef _MSC_VER + #define nedblksize _msize + #endif +#endif + +#ifndef NO_MALLINFO +#define NO_MALLINFO 0 +#endif + +#if !NO_MALLINFO +struct mallinfo; +#endif + +#if defined(__cplusplus) + #if !defined(NO_NED_NAMESPACE) +namespace nedalloc { + #else +extern "C" { + #endif + #define THROWSPEC throw() +#else + #define THROWSPEC +#endif + +/* These are the global functions */ + +/* Gets the usable size of an allocated block. Note this will always be bigger than what was +asked for due to rounding etc. +*/ +EXTSPEC size_t nedblksize(void *mem) THROWSPEC; + +EXTSPEC void nedsetvalue(void *v) THROWSPEC; + +EXTSPEC MALLOCATTR void * nedmalloc(size_t size) THROWSPEC; +EXTSPEC MALLOCATTR void * nedcalloc(size_t no, size_t size) THROWSPEC; +EXTSPEC MALLOCATTR void * nedrealloc(void *mem, size_t size) THROWSPEC; +EXTSPEC void nedfree(void *mem) THROWSPEC; +EXTSPEC MALLOCATTR void * nedmemalign(size_t alignment, size_t bytes) THROWSPEC; +#if !NO_MALLINFO +EXTSPEC struct mallinfo nedmallinfo(void) THROWSPEC; +#endif +EXTSPEC int nedmallopt(int parno, int value) THROWSPEC; +EXTSPEC int nedmalloc_trim(size_t pad) THROWSPEC; +EXTSPEC void nedmalloc_stats(void) THROWSPEC; +EXTSPEC size_t nedmalloc_footprint(void) THROWSPEC; +EXTSPEC MALLOCATTR void **nedindependent_calloc(size_t elemsno, size_t elemsize, void **chunks) THROWSPEC; +EXTSPEC MALLOCATTR void **nedindependent_comalloc(size_t elems, size_t *sizes, void **chunks) THROWSPEC; + +/* These are the pool functions */ +struct nedpool_t; +typedef struct nedpool_t nedpool; + +/* Creates a memory pool for use with the nedp* functions below. +Capacity is how much to allocate immediately (if you know you'll be allocating a lot +of memory very soon) which you can leave at zero. Threads specifies how many threads +will *normally* be accessing the pool concurrently. Setting this to zero means it +extends on demand, but be careful of this as it can rapidly consume system resources +where bursts of concurrent threads use a pool at once. +*/ +EXTSPEC MALLOCATTR nedpool *nedcreatepool(size_t capacity, int threads) THROWSPEC; + +/* Destroys a memory pool previously created by nedcreatepool(). +*/ +EXTSPEC void neddestroypool(nedpool *p) THROWSPEC; + +/* Sets a value to be associated with a pool. You can retrieve this value by passing +any memory block allocated from that pool. +*/ +EXTSPEC void nedpsetvalue(nedpool *p, void *v) THROWSPEC; +/* Gets a previously set value using nedpsetvalue() or zero if memory is unknown. +Optionally can also retrieve pool. +*/ +EXTSPEC void *nedgetvalue(nedpool **p, void *mem) THROWSPEC; + +/* Disables the thread cache for the calling thread, returning any existing cache +data to the central pool. +*/ +EXTSPEC void neddisablethreadcache(nedpool *p) THROWSPEC; + +EXTSPEC MALLOCATTR void * nedpmalloc(nedpool *p, size_t size) THROWSPEC; +EXTSPEC MALLOCATTR void * nedpcalloc(nedpool *p, size_t no, size_t size) THROWSPEC; +EXTSPEC MALLOCATTR void * nedprealloc(nedpool *p, void *mem, size_t size) THROWSPEC; +EXTSPEC void nedpfree(nedpool *p, void *mem) THROWSPEC; +EXTSPEC MALLOCATTR void * nedpmemalign(nedpool *p, size_t alignment, size_t bytes) THROWSPEC; +#if !NO_MALLINFO +EXTSPEC struct mallinfo nedpmallinfo(nedpool *p) THROWSPEC; +#endif +EXTSPEC int nedpmallopt(nedpool *p, int parno, int value) THROWSPEC; +EXTSPEC int nedpmalloc_trim(nedpool *p, size_t pad) THROWSPEC; +EXTSPEC void nedpmalloc_stats(nedpool *p) THROWSPEC; +EXTSPEC size_t nedpmalloc_footprint(nedpool *p) THROWSPEC; +EXTSPEC MALLOCATTR void **nedpindependent_calloc(nedpool *p, size_t elemsno, size_t elemsize, void **chunks) THROWSPEC; +EXTSPEC MALLOCATTR void **nedpindependent_comalloc(nedpool *p, size_t elems, size_t *sizes, void **chunks) THROWSPEC; + +#if defined(__cplusplus) +} +#endif + +#undef MALLOCATTR +#undef EXTSPEC + +#endif diff --git a/compat/regex/regex.c b/compat/regex/regex.c index 87b33e4669..5ea007567d 100644 --- a/compat/regex/regex.c +++ b/compat/regex/regex.c @@ -1043,7 +1043,7 @@ regex_compile (pattern, size, syntax, bufp) they can be reliably used as array indices. */ register unsigned char c, c1; - /* A random tempory spot in PATTERN. */ + /* A random temporary spot in PATTERN. */ const char *p1; /* Points to the end of the buffer, where we should append. */ @@ -1796,7 +1796,7 @@ regex_compile (pattern, size, syntax, bufp) we're all done, the pattern will look like: set_number_at <jump count> <upper bound> set_number_at <succeed_n count> <lower bound> - succeed_n <after jump addr> <succed_n count> + succeed_n <after jump addr> <succeed_n count> <body of loop> jump_n <succeed_n addr> <jump count> (The upper bound and `jump_n' are omitted if diff --git a/compat/snprintf.c b/compat/snprintf.c index 357e733074..6c0fb056a5 100644 --- a/compat/snprintf.c +++ b/compat/snprintf.c @@ -6,8 +6,12 @@ * number of characters to write without the trailing NUL. */ #ifndef SNPRINTF_SIZE_CORR +#if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ < 4 +#define SNPRINTF_SIZE_CORR 1 +#else #define SNPRINTF_SIZE_CORR 0 #endif +#endif #undef vsnprintf int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap) diff --git a/compat/win32mmap.c b/compat/win32mmap.c new file mode 100644 index 0000000000..779d796cd5 --- /dev/null +++ b/compat/win32mmap.c @@ -0,0 +1,53 @@ +#include "../git-compat-util.h" + +/* + * Note that this doesn't return the actual pagesize, but + * the allocation granularity. If future Windows specific git code + * needs the real getpagesize function, we need to find another solution. + */ +int mingw_getpagesize(void) +{ + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwAllocationGranularity; +} + +void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset) +{ + HANDLE hmap; + void *temp; + size_t len; + struct stat st; + uint64_t o = offset; + uint32_t l = o & 0xFFFFFFFF; + uint32_t h = (o >> 32) & 0xFFFFFFFF; + + if (!fstat(fd, &st)) + len = xsize_t(st.st_size); + else + die("mmap: could not determine filesize"); + + if ((length + offset) > len) + length = len - offset; + + if (!(flags & MAP_PRIVATE)) + die("Invalid usage of mmap when built with USE_WIN32_MMAP"); + + hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY, + 0, 0, 0); + + if (!hmap) + return MAP_FAILED; + + temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start); + + if (!CloseHandle(hmap)) + warning("unable to close file mapping handle\n"); + + return temp ? temp : MAP_FAILED; +} + +int git_munmap(void *start, size_t length) +{ + return !UnmapViewOfFile(start); +} diff --git a/compat/winansi.c b/compat/winansi.c index e2d96dfe6f..9217c24b43 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -18,8 +18,6 @@ This file is git-specific. Therefore, this file does not attempt to implement any codes that are not used by git. - - TODO: K */ static HANDLE console; @@ -79,6 +77,21 @@ static void set_console_attr(void) SetConsoleTextAttribute(console, attributes); } +static void erase_in_line(void) +{ + CONSOLE_SCREEN_BUFFER_INFO sbi; + DWORD dummy; /* Needed for Windows 7 (or Vista) regression */ + + if (!console) + return; + + GetConsoleScreenBufferInfo(console, &sbi); + FillConsoleOutputCharacterA(console, ' ', + sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition, + &dummy); +} + + static const char *set_attr(const char *str) { const char *func; @@ -218,7 +231,7 @@ static const char *set_attr(const char *str) set_console_attr(); break; case 'K': - /* TODO */ + erase_in_line(); break; default: /* Unsupported code */ @@ -331,9 +331,9 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool) return 1; if (!*value) return 0; - if (!strcasecmp(value, "true") || !strcasecmp(value, "yes")) + if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) return 1; - if (!strcasecmp(value, "false") || !strcasecmp(value, "no")) + if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off")) return 0; *is_bool = 0; return git_config_int(name, value); @@ -495,6 +495,16 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.createobject")) { + if (!strcmp(value, "rename")) + object_creation_mode = OBJECT_CREATION_USES_RENAMES; + else if (!strcmp(value, "link")) + object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; + else + die("Invalid mode for object creation: %s", value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@ -565,6 +575,31 @@ static int git_default_branch_config(const char *var, const char *value) return 0; } +static int git_default_push_config(const char *var, const char *value) +{ + if (!strcmp(var, "push.default")) { + if (!value) + return config_error_nonbool(var); + else if (!strcmp(value, "nothing")) + push_default = PUSH_DEFAULT_NOTHING; + else if (!strcmp(value, "matching")) + push_default = PUSH_DEFAULT_MATCHING; + else if (!strcmp(value, "tracking")) + push_default = PUSH_DEFAULT_TRACKING; + else if (!strcmp(value, "current")) + push_default = PUSH_DEFAULT_CURRENT; + else { + error("Malformed value for %s: %s", var, value); + return error("Must be one of nothing, matching, " + "tracking or current."); + } + return 0; + } + + /* Add other config variables here and to Documentation/config.txt. */ + return 0; +} + static int git_default_mailmap_config(const char *var, const char *value) { if (!strcmp(var, "mailmap.file")) @@ -588,6 +623,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, "push.")) + return git_default_push_config(var, value); + if (!prefixcmp(var, "mailmap.")) return git_default_mailmap_config(var, value); @@ -644,28 +682,37 @@ int git_config_global(void) int git_config(config_fn_t fn, void *data) { - int ret = 0; + int ret = 0, found = 0; char *repo_config = NULL; const char *home = NULL; /* Setting $GIT_CONFIG makes git read _only_ the given config file. */ if (config_exclusive_filename) return git_config_from_file(fn, config_exclusive_filename, data); - if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) + if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) { ret += git_config_from_file(fn, git_etc_gitconfig(), data); + found += 1; + } home = getenv("HOME"); if (git_config_global() && home) { char *user_config = xstrdup(mkpath("%s/.gitconfig", home)); - if (!access(user_config, R_OK)) + if (!access(user_config, R_OK)) { ret += git_config_from_file(fn, user_config, data); + found += 1; + } free(user_config); } repo_config = git_pathdup("config"); - ret += git_config_from_file(fn, repo_config, data); + if (!access(repo_config, R_OK)) { + ret += git_config_from_file(fn, repo_config, data); + found += 1; + } free(repo_config); + if (found == 0) + return -1; return ret; } @@ -677,16 +724,16 @@ int git_config(config_fn_t fn, void *data) static struct { int baselen; - char* key; + char *key; int do_not_match; - regex_t* value_regex; + regex_t *value_regex; int multi_replace; size_t offset[MAX_MATCHES]; enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state; int seen; } store; -static int matches(const char* key, const char* value) +static int matches(const char *key, const char *value) { return !strcmp(key, store.key) && (store.value_regex == NULL || @@ -694,7 +741,7 @@ static int matches(const char* key, const char* value) !regexec(store.value_regex, value, 0, NULL, 0))); } -static int store_aux(const char* key, const char* value, void *cb) +static int store_aux(const char *key, const char *value, void *cb) { const char *ep; size_t section_len; @@ -763,7 +810,7 @@ static int write_error(const char *filename) return 4; } -static int store_write_section(int fd, const char* key) +static int store_write_section(int fd, const char *key) { const char *dot; int i, success; @@ -788,7 +835,7 @@ static int store_write_section(int fd, const char* key) return success; } -static int store_write_pair(int fd, const char* key, const char* value) +static int store_write_pair(int fd, const char *key, const char *value) { int i, success; int length = strlen(key + store.baselen + 1); @@ -836,8 +883,8 @@ static int store_write_pair(int fd, const char* key, const char* value) return success; } -static ssize_t find_beginning_of_line(const char* contents, size_t size, - size_t offset_, int* found_bracket) +static ssize_t find_beginning_of_line(const char *contents, size_t size, + size_t offset_, int *found_bracket) { size_t equal_offset = size, bracket_offset = size; ssize_t offset; @@ -862,7 +909,7 @@ contline: return offset; } -int git_config_set(const char* key, const char* value) +int git_config_set(const char *key, const char *value) { return git_config_set_multivar(key, value, NULL, 0); } @@ -890,15 +937,15 @@ int git_config_set(const char* key, const char* value) * - the config file is removed and the lock file rename()d to it. * */ -int git_config_set_multivar(const char* key, const char* value, - const char* value_regex, int multi_replace) +int git_config_set_multivar(const char *key, const char *value, + const char *value_regex, int multi_replace) { int i, dot; int fd = -1, in_fd; int ret; - char* config_filename; + char *config_filename; struct lock_file *lock = NULL; - const char* last_dot = strrchr(key, '.'); + const char *last_dot = strrchr(key, '.'); if (config_exclusive_filename) config_filename = xstrdup(config_exclusive_filename); @@ -979,13 +1026,13 @@ int git_config_set_multivar(const char* key, const char* value, goto out_free; } - store.key = (char*)key; + store.key = (char *)key; if (!store_write_section(fd, key) || !store_write_pair(fd, key, value)) goto write_err_out; } else { struct stat st; - char* contents; + char *contents; size_t contents_sz, copy_begin, copy_end; int i, new_line = 0; diff --git a/config.mak.in b/config.mak.in index 7cce0c12d5..dd60451318 100644 --- a/config.mak.in +++ b/config.mak.in @@ -30,8 +30,10 @@ NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ NO_OPENSSL=@NO_OPENSSL@ NO_CURL=@NO_CURL@ NO_EXPAT=@NO_EXPAT@ +NO_LIBGEN_H=@NO_LIBGEN_H@ NEEDS_LIBICONV=@NEEDS_LIBICONV@ NEEDS_SOCKET=@NEEDS_SOCKET@ +NEEDS_RESOLV=@NEEDS_RESOLV@ NO_SYS_SELECT_H=@NO_SYS_SELECT_H@ NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@ @@ -46,6 +48,7 @@ NO_STRTOUMAX=@NO_STRTOUMAX@ NO_SETENV=@NO_SETENV@ NO_UNSETENV=@NO_UNSETENV@ NO_MKDTEMP=@NO_MKDTEMP@ +NO_MKSTEMPS=@NO_MKSTEMPS@ NO_ICONV=@NO_ICONV@ OLD_ICONV=@OLD_ICONV@ NO_DEFLATE_BOUND=@NO_DEFLATE_BOUND@ diff --git a/configure.ac b/configure.ac index 082a03d3cf..7937e604be 100644 --- a/configure.ac +++ b/configure.ac @@ -42,6 +42,8 @@ else \ if test "$withval" = "yes"; then \ AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ else \ + m4_toupper($1)_PATH=$withval; \ + AC_MSG_NOTICE([Setting m4_toupper($1)_PATH to $withval]); \ GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \ fi; \ fi; \ @@ -61,6 +63,8 @@ elif test "$withval" = "yes"; then \ m4_toupper(NO_$1)=; \ else \ m4_toupper(NO_$1)=; \ + m4_toupper($1)DIR=$withval; \ + AC_MSG_NOTICE([Setting m4_toupper($1)DIR to $withval]); \ GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ fi \ ])# GIT_PARSE_WITH @@ -76,6 +80,32 @@ AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[ AC_SEARCH_LIBS([$1],, [$2],[$3]) ],[$3])]) + +dnl +dnl GIT_STASH_FLAGS(BASEPATH_VAR) +dnl ----------------------------- +dnl Allow for easy stashing of LDFLAGS and CPPFLAGS before running +dnl tests that may want to take user settings into account. +AC_DEFUN([GIT_STASH_FLAGS],[ +if test -n "$1"; then + old_CPPFLAGS="$CPPFLAGS" + old_LDFLAGS="$LDFLAGS" + CPPFLAGS="-I$1/include $CPPFLAGS" + LDFLAGS="-L$1/$lib $LDFLAGS" +fi +]) + +dnl +dnl GIT_UNSTASH_FLAGS(BASEPATH_VAR) +dnl ----------------------------- +dnl Restore the stashed *FLAGS values. +AC_DEFUN([GIT_UNSTASH_FLAGS],[ +if test -n "$1"; then + CPPFLAGS="$old_CPPFLAGS" + LDFLAGS="$old_LDFLAGS" +fi +]) + ## Site configuration related to programs (before tests) ## --with-PACKAGE[=ARG] and --without-PACKAGE # @@ -86,9 +116,124 @@ AC_ARG_WITH([lib], [if test "$withval" = "no" || test "$withval" = "yes"; then \ AC_MSG_WARN([You should provide name for --with-lib=ARG]); \ else \ + lib=$withval; \ + AC_MSG_NOTICE([Setting lib to '$lib']); \ GIT_CONF_APPEND_LINE(lib=$withval); \ fi; \ ],[]) + +if test -z "$lib"; then + AC_MSG_NOTICE([Setting lib to 'lib' (the default)]) + lib=lib +fi + +AC_ARG_ENABLE([pthreads], + [AS_HELP_STRING([--enable-pthreads=FLAGS], + [FLAGS is the value to pass to the compiler to enable POSIX Threads.] + [The default if FLAGS is not specified is to try first -pthread] + [and then -lpthread.] + [--without-pthreads will disable threading.])], +[ +if test "x$enableval" = "xyes"; then + AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads]) +elif test "x$enableval" != "xno"; then + PTHREAD_CFLAGS=$enableval + AC_MSG_NOTICE([Setting '$PTHREAD_CFLAGS' as the FLAGS to enable POSIX Threads]) +else + AC_MSG_NOTICE([POSIX Threads will be disabled.]) + NO_PTHREADS=YesPlease + USER_NOPTHREAD=1 +fi], +[ + AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads.]) +]) + +## Site configuration (override autodetection) +## --with-PACKAGE[=ARG] and --without-PACKAGE +AC_MSG_NOTICE([CHECKS for site configuration]) +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability +# tests. These tests take up a significant amount of the total test time +# but are not needed unless you plan to talk to SVN repos. +# +# Define MOZILLA_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast +# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default +# choice) has very fast version optimized for i586. +# +# Define PPC_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for PowerPC. +# +# Define ARM_SHA1 environment variable when running make to make use of +# a bundled SHA1 routine optimized for ARM. +# +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. +# +# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(openssl, +AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) +AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ +GIT_PARSE_WITH(openssl)) +# +# Define NO_CURL if you do not have curl installed. git-http-pull and +# git-http-push are not built, and you cannot use http:// and https:// +# transports. +# +# Define CURLDIR=/foo/bar if your curl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(curl, +AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for curl library and headers]), +GIT_PARSE_WITH(curl)) +# +# 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. +AC_ARG_WITH(expat, +AS_HELP_STRING([--with-expat], +[support git-push using http:// and https:// transports via WebDAV (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for expat library and headers]), +GIT_PARSE_WITH(expat)) +# +# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink +# installed in /sw, but don't want GIT to link against any libraries +# installed there. If defined you may specify your own (or Fink's) +# include directories and library directories by defining CFLAGS +# and LDFLAGS appropriately. +# +# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, +# have DarwinPorts installed in /opt/local, but don't want GIT to +# link against any libraries installed there. If defined you may +# specify your own (or DarwinPort's) include directories and +# library directories by defining CFLAGS and LDFLAGS appropriately. +# +# Define NO_MMAP if you want to avoid mmap. +# +# Define NO_ICONV if your libc does not properly support iconv. +AC_ARG_WITH(iconv, +AS_HELP_STRING([--without-iconv], +[if your architecture doesn't properly support iconv]) +AS_HELP_STRING([--with-iconv=PATH], +[PATH is prefix for libiconv library and headers]) +AS_HELP_STRING([], +[used only if you need linking with libiconv]), +GIT_PARSE_WITH(iconv)) + +## --enable-FEATURE[=ARG] and --disable-FEATURE +# +# Define USE_NSEC below if you want git to care about sub-second file mtimes +# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and +# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely +# randomly break unless your underlying filesystem supports those sub-second +# times (my ext3 doesn't). +# +# Define USE_STDEV below if you want git to care about the underlying device +# change being considered an inode change from the update-index perspective. + # # Define SHELL_PATH to provide path to shell. GIT_ARG_SET_PATH(shell) @@ -167,7 +312,7 @@ fi AC_CHECK_PROGS(ASCIIDOC, [asciidoc]) if test -n "$ASCIIDOC"; then AC_MSG_CHECKING([for asciidoc version]) - asciidoc_version=`$ASCIIDOC --version 2>&1` + asciidoc_version=`$ASCIIDOC --version 2>/dev/null` case "${asciidoc_version}" in asciidoc' '8*) ASCIIDOC8=YesPlease @@ -191,33 +336,57 @@ AC_MSG_NOTICE([CHECKS for libraries]) # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). + +GIT_STASH_FLAGS($OPENSSLDIR) + AC_CHECK_LIB([crypto], [SHA1_Init], [NEEDS_SSL_WITH_CRYPTO=], [AC_CHECK_LIB([ssl], [SHA1_Init], [NEEDS_SSL_WITH_CRYPTO=YesPlease NEEDS_SSL_WITH_CRYPTO=], [NO_OPENSSL=YesPlease])]) + +GIT_UNSTASH_FLAGS($OPENSSLDIR) + AC_SUBST(NEEDS_SSL_WITH_CRYPTO) AC_SUBST(NO_OPENSSL) + # # Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. + +GIT_STASH_FLAGS($CURLDIR) + AC_CHECK_LIB([curl], [curl_global_init], [NO_CURL=], [NO_CURL=YesPlease]) + +GIT_UNSTASH_FLAGS($CURLDIR) + AC_SUBST(NO_CURL) + # # 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. + +GIT_STASH_FLAGS($EXPATDIR) + AC_CHECK_LIB([expat], [XML_ParserCreate], [NO_EXPAT=], [NO_EXPAT=YesPlease]) + +GIT_UNSTASH_FLAGS($EXPATDIR) + AC_SUBST(NO_EXPAT) + # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin and # some Solaris installations). # Define NO_ICONV if neither libc nor libiconv support iconv. + +GIT_STASH_FLAGS($ICONVDIR) + AC_DEFUN([ICONVTEST_SRC], [ #include <iconv.h> @@ -227,25 +396,46 @@ int main(void) return 0; } ]) -AC_MSG_CHECKING([for iconv in -lc]) -AC_LINK_IFELSE(ICONVTEST_SRC, + +if test -n "$ICONVDIR"; then + lib_order="-liconv -lc" +else + lib_order="-lc -liconv" +fi + +NO_ICONV=YesPlease + +for l in $lib_order; do + if test "$l" = "-liconv"; then + NEEDS_LIBICONV=YesPlease + else + NEEDS_LIBICONV= + fi + + old_LIBS="$LIBS" + LIBS="$LIBS $l" + AC_MSG_CHECKING([for iconv in $l]) + AC_LINK_IFELSE(ICONVTEST_SRC, [AC_MSG_RESULT([yes]) - NEEDS_LIBICONV=], - [AC_MSG_RESULT([no]) - old_LIBS="$LIBS" - LIBS="$LIBS -liconv" - AC_MSG_CHECKING([for iconv in -liconv]) - AC_LINK_IFELSE(ICONVTEST_SRC, - [AC_MSG_RESULT([yes]) - NEEDS_LIBICONV=YesPlease], - [AC_MSG_RESULT([no]) - NO_ICONV=YesPlease]) - LIBS="$old_LIBS"]) + NO_ICONV= + break], + [AC_MSG_RESULT([no])]) + LIBS="$old_LIBS" +done + +#in case of break +LIBS="$old_LIBS" + +GIT_UNSTASH_FLAGS($ICONVDIR) + AC_SUBST(NEEDS_LIBICONV) AC_SUBST(NO_ICONV) -test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv" + # # Define NO_DEFLATE_BOUND if deflateBound is missing from zlib. + +GIT_STASH_FLAGS($ZLIB_PATH) + AC_DEFUN([ZLIBTEST_SRC], [ #include <zlib.h> @@ -263,7 +453,11 @@ AC_LINK_IFELSE(ZLIBTEST_SRC, [AC_MSG_RESULT([no]) NO_DEFLATE_BOUND=yes]) LIBS="$old_LIBS" + +GIT_UNSTASH_FLAGS($ZLIB_PATH) + AC_SUBST(NO_DEFLATE_BOUND) + # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). @@ -273,6 +467,15 @@ AC_CHECK_LIB([c], [socket], AC_SUBST(NEEDS_SOCKET) test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" +# +# Define NEEDS_RESOLV if linking with -lnsl and/or -lsocket is not enough. +# Notably on Solaris hstrerror resides in libresolv and on Solaris 7 +# inet_ntop and inet_pton additionally reside there. +AC_CHECK_LIB([resolv], [hstrerror], +[NEEDS_RESOLV=], +[NEEDS_RESOLV=YesPlease]) +AC_SUBST(NEEDS_RESOLV) +test -n "$NEEDS_RESOLV" && LIBS="$LIBS -lresolv" ## Checks for header files. AC_MSG_NOTICE([CHECKS for header files]) @@ -297,13 +500,18 @@ int main(void) return 0; } ]]) + +GIT_STASH_FLAGS($ICONVDIR) + AC_MSG_CHECKING([for old iconv()]) AC_COMPILE_IFELSE(OLDICONVTEST_SRC, [AC_MSG_RESULT([no])], [AC_MSG_RESULT([yes]) OLD_ICONV=UnfortunatelyYes]) -AC_SUBST(OLD_ICONV) +GIT_UNSTASH_FLAGS($ICONVDIR) + +AC_SUBST(OLD_ICONV) ## Checks for typedefs, structures, and compiler characteristics. AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) @@ -428,6 +636,12 @@ AC_SUBST(SNPRINTF_RETURNS_BOGUS) ## (in default C library and libraries checked by AC_CHECK_LIB) AC_MSG_NOTICE([CHECKS for library functions]) # +# Define NO_LIBGEN_H if you don't have libgen.h. +AC_CHECK_HEADER([libgen.h], +[NO_LIBGEN_H=], +[NO_LIBGEN_H=YesPlease]) +AC_SUBST(NO_LIBGEN_H) +# # Define NO_STRCASESTR if you don't have strcasestr. GIT_CHECK_FUNC(strcasestr, [NO_STRCASESTR=], @@ -478,6 +692,13 @@ GIT_CHECK_FUNC(mkdtemp, [NO_MKDTEMP=YesPlease]) AC_SUBST(NO_MKDTEMP) # +# Define NO_MKSTEMPS if you don't have mkstemps in the C library. +GIT_CHECK_FUNC(mkstemps, +[NO_MKSTEMPS=], +[NO_MKSTEMPS=YesPlease]) +AC_SUBST(NO_MKSTEMPS) +# +# # Define NO_MMAP if you want to avoid mmap. # # Define NO_ICONV if your libc does not properly support iconv. @@ -494,114 +715,65 @@ AC_SUBST(NO_MKDTEMP) # # 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;]] -)]) -${CC} -pthread conftest.c -o conftest.o > /dev/null 2>&1 -if test $? -eq 0;then - PTHREAD_LIBS="-pthread" - THREADED_DELTA_SEARCH=YesPlease +AC_DEFUN([PTHREADTEST_SRC], [ +#include <pthread.h> + +int main(void) +{ + pthread_mutex_t test_mutex; + return (0); +} +]) + +dnl AC_LANG_CONFTEST([AC_LANG_PROGRAM( +dnl [[#include <pthread.h>]], +dnl [[pthread_mutex_t test_mutex;]] +dnl )]) + +NO_PTHREADS=UnfortunatelyYes +THREADED_DELTA_SEARCH= +PTHREAD_LIBS= + +if test -n "$USER_NOPTHREAD"; then + AC_MSG_NOTICE([Skipping POSIX Threads at user request.]) +# handle these separately since PTHREAD_CFLAGS could be '-lpthreads +# -D_REENTRANT' or some such. +elif test -z "$PTHREAD_CFLAGS"; then + for opt in -pthread -lpthread; do + old_CFLAGS="$CFLAGS" + CFLAGS="$opt $CFLAGS" + AC_MSG_CHECKING([Checking for POSIX Threads with '$opt']) + AC_LINK_IFELSE(PTHREADTEST_SRC, + [AC_MSG_RESULT([yes]) + NO_PTHREADS= + PTHREAD_LIBS="$opt" + THREADED_DELTA_SEARCH=YesPlease + break + ], + [AC_MSG_RESULT([no])]) + CFLAGS="$old_CFLAGS" + done 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 + old_CFLAGS="$CFLAGS" + CFLAGS="$PTHREAD_CFLAGS $CFLAGS" + AC_MSG_CHECKING([Checking for POSIX Threads with '$PTHREAD_CFLAGS']) + AC_LINK_IFELSE(PTHREADTEST_SRC, + [AC_MSG_RESULT([yes]) + NO_PTHREADS= + PTHREAD_LIBS="$PTHREAD_CFLAGS" + THREADED_DELTA_SEARCH=YesPlease + ], + [AC_MSG_RESULT([no])]) + + CFLAGS="$old_CFLAGS" fi + +CFLAGS="$old_CFLAGS" + AC_SUBST(PTHREAD_LIBS) AC_SUBST(NO_PTHREADS) AC_SUBST(THREADED_DELTA_SEARCH) -## Site configuration (override autodetection) -## --with-PACKAGE[=ARG] and --without-PACKAGE -AC_MSG_NOTICE([CHECKS for site configuration]) -# -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability -# tests. These tests take up a significant amount of the total test time -# but are not needed unless you plan to talk to SVN repos. -# -# Define MOZILLA_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast -# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default -# choice) has very fast version optimized for i586. -# -# Define PPC_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for PowerPC. -# -# Define ARM_SHA1 environment variable when running make to make use of -# a bundled SHA1 routine optimized for ARM. -# -# Define NO_OPENSSL environment variable if you do not have OpenSSL. -# This also implies MOZILLA_SHA1. -# -# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in -# /foo/bar/include and /foo/bar/lib directories. -AC_ARG_WITH(openssl, -AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) -AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ -GIT_PARSE_WITH(openssl)) -# -# Define NO_CURL if you do not have curl installed. git-http-pull and -# git-http-push are not built, and you cannot use http:// and https:// -# transports. -# -# Define CURLDIR=/foo/bar if your curl header and library files are in -# /foo/bar/include and /foo/bar/lib directories. -AC_ARG_WITH(curl, -AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)]) -AS_HELP_STRING([], [ARG can be also prefix for curl library and headers]), -GIT_PARSE_WITH(curl)) -# -# 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. -AC_ARG_WITH(expat, -AS_HELP_STRING([--with-expat], -[support git-push using http:// and https:// transports via WebDAV (default is YES)]) -AS_HELP_STRING([], [ARG can be also prefix for expat library and headers]), -GIT_PARSE_WITH(expat)) -# -# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink -# installed in /sw, but don't want GIT to link against any libraries -# installed there. If defined you may specify your own (or Fink's) -# include directories and library directories by defining CFLAGS -# and LDFLAGS appropriately. -# -# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, -# have DarwinPorts installed in /opt/local, but don't want GIT to -# link against any libraries installed there. If defined you may -# specify your own (or DarwinPort's) include directories and -# library directories by defining CFLAGS and LDFLAGS appropriately. -# -# Define NO_MMAP if you want to avoid mmap. -# -# Define NO_ICONV if your libc does not properly support iconv. -AC_ARG_WITH(iconv, -AS_HELP_STRING([--without-iconv], -[if your architecture doesn't properly support iconv]) -AS_HELP_STRING([--with-iconv=PATH], -[PATH is prefix for libiconv library and headers]) -AS_HELP_STRING([], -[used only if you need linking with libiconv]), -GIT_PARSE_WITH(iconv)) - -## --enable-FEATURE[=ARG] and --disable-FEATURE -# -# Define USE_NSEC below if you want git to care about sub-second file mtimes -# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and -# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely -# randomly break unless your underlying filesystem supports those sub-second -# times (my ext3 doesn't). -# -# Define USE_STDEV below if you want git to care about the underlying device -# change being considered an inode change from the update-index perspective. - - ## Output files AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"]) AC_OUTPUT @@ -177,18 +177,11 @@ static enum protocol get_protocol(const char *name) static const char *ai_name(const struct addrinfo *ai) { - static char addr[INET_ADDRSTRLEN]; - if ( AF_INET == ai->ai_family ) { - struct sockaddr_in *in; - in = (struct sockaddr_in *)ai->ai_addr; - inet_ntop(ai->ai_family, &in->sin_addr, addr, sizeof(addr)); - } else if ( AF_INET6 == ai->ai_family ) { - struct sockaddr_in6 *in; - in = (struct sockaddr_in6 *)ai->ai_addr; - inet_ntop(ai->ai_family, &in->sin6_addr, addr, sizeof(addr)); - } else { + static char addr[NI_MAXHOST]; + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0, + NI_NUMERICHOST) != 0) strcpy(addr, "(unknown)"); - } + return addr; } @@ -373,8 +366,6 @@ static void git_tcp_connect(int fd[2], char *host, int flags) static char *git_proxy_command; -static const char *rhost_name; -static int rhost_len; static int git_proxy_command_options(const char *var, const char *value, void *cb) @@ -383,6 +374,8 @@ static int git_proxy_command_options(const char *var, const char *value, const char *for_pos; int matchlen = -1; int hostlen; + const char *rhost_name = cb; + int rhost_len = strlen(rhost_name); if (git_proxy_command) return 0; @@ -426,11 +419,8 @@ static int git_proxy_command_options(const char *var, const char *value, static int git_use_proxy(const char *host) { - rhost_name = host; - rhost_len = strlen(host); git_proxy_command = getenv("GIT_PROXY_COMMAND"); - git_config(git_proxy_command_options, NULL); - rhost_name = NULL; + git_config(git_proxy_command_options, (void*)host); return (git_proxy_command && *git_proxy_command); } @@ -507,7 +497,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig, const char *prog, int flags) { char *url = xstrdup(url_orig); - char *host, *path = url; + char *host, *path; char *end; int c; struct child_process *conn; @@ -589,7 +579,10 @@ struct child_process *git_connect(int fd[2], const char *url_orig, git_tcp_connect(fd, host, flags); /* * Separate original protocol components prog and path - * from extended components with a NUL byte. + * from extended host header with a NUL byte. + * + * Note: Do not add any other headers here! Doing so + * will cause older git-daemon servers to crash. */ packet_write(fd[1], "%s %s%chost=%s%c", @@ -612,14 +605,18 @@ struct child_process *git_connect(int fd[2], const char *url_orig, die("command line too long"); conn->in = conn->out = -1; - conn->argv = arg = xcalloc(6, sizeof(*arg)); + conn->argv = arg = xcalloc(7, sizeof(*arg)); if (protocol == PROTO_SSH) { const char *ssh = getenv("GIT_SSH"); + int putty = ssh && strcasestr(ssh, "plink"); if (!ssh) ssh = "ssh"; *arg++ = ssh; + if (putty && !strcasestr(ssh, "tortoiseplink")) + *arg++ = "-batch"; if (port) { - *arg++ = "-p"; + /* P is for PuTTY, p is for OpenSSH */ + *arg++ = putty ? "-P" : "-p"; *arg++ = port; } *arg++ = host; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 8431837f97..80ab4e45eb 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -40,6 +40,10 @@ # with the bash.showDirtyState variable, which defaults to true # once GIT_PS1_SHOWDIRTYSTATE is enabled. # +# You can also see if currently something is stashed, by setting +# GIT_PS1_SHOWSTASHSTATE to a nonempty value. If something is stashed, +# then a '$' will be shown next to the branch name. +# # To submit patches: # # *) Read Documentation/SubmittingPatches @@ -62,7 +66,7 @@ esac __gitdir () { if [ -z "${1-}" ]; then - if [ -n "$__git_dir" ]; then + if [ -n "${__git_dir-}" ]; then echo "$__git_dir" elif [ -d .git ]; then echo .git @@ -80,68 +84,84 @@ __gitdir () # returns text to add to bash PS1 prompt (includes branch name) __git_ps1 () { - local g="$(git rev-parse --git-dir 2>/dev/null)" + local g="$(__gitdir)" if [ -n "$g" ]; then local r local b - if [ -d "$g/rebase-apply" ] - then - if test -f "$g/rebase-apply/rebasing" - then - r="|REBASE" - elif test -f "$g/rebase-apply/applying" - then - r="|AM" - else - r="|AM/REBASE" - fi - b="$(git symbolic-ref HEAD 2>/dev/null)" - elif [ -f "$g/rebase-merge/interactive" ] - then + if [ -f "$g/rebase-merge/interactive" ]; then r="|REBASE-i" b="$(cat "$g/rebase-merge/head-name")" - elif [ -d "$g/rebase-merge" ] - then + elif [ -d "$g/rebase-merge" ]; then r="|REBASE-m" b="$(cat "$g/rebase-merge/head-name")" - elif [ -f "$g/MERGE_HEAD" ] - then - r="|MERGING" - b="$(git symbolic-ref HEAD 2>/dev/null)" else - if [ -f "$g/BISECT_LOG" ] - then - r="|BISECTING" - fi - if ! b="$(git symbolic-ref HEAD 2>/dev/null)" - then - if ! b="$(git describe --exact-match HEAD 2>/dev/null)" - then - b="$(cut -c1-7 "$g/HEAD")..." + if [ -d "$g/rebase-apply" ]; then + if [ -f "$g/rebase-apply/rebasing" ]; then + r="|REBASE" + elif [ -f "$g/rebase-apply/applying" ]; then + r="|AM" + else + r="|AM/REBASE" fi + elif [ -f "$g/MERGE_HEAD" ]; then + r="|MERGING" + elif [ -f "$g/BISECT_LOG" ]; then + r="|BISECTING" fi + + b="$(git symbolic-ref HEAD 2>/dev/null)" || { + + b="$( + case "${GIT_PS1_DESCRIBE_STYLE-}" in + (contains) + git describe --contains HEAD ;; + (branch) + git describe --contains --all HEAD ;; + (describe) + git describe HEAD ;; + (* | default) + git describe --exact-match HEAD ;; + esac 2>/dev/null)" || + + b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." || + b="unknown" + b="($b)" + } fi local w local i + local s + local c - 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="#" + if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then + if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then + c="BARE:" + else + b="GIT_DIR!" + fi + elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then + if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then + if [ "$(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 "${GIT_PS1_SHOWSTASHSTATE-}" ]; then + git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$" + fi fi if [ -n "${1-}" ]; then - printf "$1" "${b##refs/heads/}$w$i$r" + printf "$1" "$c${b##refs/heads/}$w$i$s$r" else - printf " (%s)" "${b##refs/heads/}$w$i$r" + printf " (%s)" "$c${b##refs/heads/}$w$i$s$r" fi fi } @@ -299,7 +319,7 @@ __git_remotes () __git_merge_strategies () { - if [ -n "$__git_merge_strategylist" ]; then + if [ -n "${__git_merge_strategylist-}" ]; then echo "$__git_merge_strategylist" return fi @@ -383,9 +403,88 @@ __git_complete_revlist () esac } +__git_complete_remote_or_refspec () +{ + local cmd="${COMP_WORDS[1]}" + local cur="${COMP_WORDS[COMP_CWORD]}" + local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0 + while [ $c -lt $COMP_CWORD ]; do + i="${COMP_WORDS[c]}" + case "$i" in + --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;; + -*) ;; + *) remote="$i"; break ;; + esac + c=$((++c)) + done + if [ -z "$remote" ]; then + __gitcomp "$(__git_remotes)" + return + fi + if [ $no_complete_refspec = 1 ]; then + COMPREPLY=() + return + fi + [ "$remote" = "." ] && remote= + case "$cur" in + *:*) + case "$COMP_WORDBREAKS" in + *:*) : great ;; + *) pfx="${cur%%:*}:" ;; + esac + cur="${cur#*:}" + lhs=0 + ;; + +*) + pfx="+" + cur="${cur#+}" + ;; + esac + case "$cmd" in + fetch) + if [ $lhs = 1 ]; then + __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur" + else + __gitcomp "$(__git_refs)" "$pfx" "$cur" + fi + ;; + pull) + if [ $lhs = 1 ]; then + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + else + __gitcomp "$(__git_refs)" "$pfx" "$cur" + fi + ;; + push) + if [ $lhs = 1 ]; then + __gitcomp "$(__git_refs)" "$pfx" "$cur" + else + __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur" + fi + ;; + esac +} + +__git_complete_strategy () +{ + case "${COMP_WORDS[COMP_CWORD-1]}" in + -s|--strategy) + __gitcomp "$(__git_merge_strategies)" + return 0 + esac + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --strategy=*) + __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" + return 0 + ;; + esac + return 1 +} + __git_all_commands () { - if [ -n "$__git_all_commandlist" ]; then + if [ -n "${__git_all_commandlist-}" ]; then echo "$__git_all_commandlist" return fi @@ -403,7 +502,7 @@ __git_all_commandlist="$(__git_all_commands 2>/dev/null)" __git_porcelain_commands () { - if [ -n "$__git_porcelain_commandlist" ]; then + if [ -n "${__git_porcelain_commandlist-}" ]; then echo "$__git_porcelain_commandlist" return fi @@ -563,7 +662,8 @@ _git_am () ;; --*) __gitcomp " - --signoff --utf8 --binary --3way --interactive + --3way --committer-date-is-author-date --ignore-date + --interactive --keep --no-utf8 --signoff --utf8 --whitespace= " return @@ -816,7 +916,7 @@ _git_diff () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - __gitcomp "--cached --pickaxe-all --pickaxe-regex + __gitcomp "--cached --staged --pickaxe-all --pickaxe-regex --base --ours --theirs $__git_diff_common_options " @@ -826,42 +926,62 @@ _git_diff () __git_complete_file } -_git_fetch () +__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff + tkdiff vimdiff gvimdiff xxdiff araxis +" + +_git_difftool () { local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --tool=*) + __gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}" + return + ;; + --*) + __gitcomp "--tool=" + return + ;; + esac + COMPREPLY=() +} - if [ "$COMP_CWORD" = 2 ]; then - __gitcomp "$(__git_remotes)" - else - case "$cur" in - *:*) - local pfx="" - case "$COMP_WORDBREAKS" in - *:*) : great ;; - *) pfx="${cur%%:*}:" ;; - esac - __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}" - ;; - *) - __gitcomp "$(__git_refs2 "${COMP_WORDS[2]}")" - ;; - esac - fi +__git_fetch_options=" + --quiet --verbose --append --upload-pack --force --keep --depth= + --tags --no-tags +" + +_git_fetch () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp "$__git_fetch_options" + return + ;; + esac + __git_complete_remote_or_refspec } _git_format_patch () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in + --thread=*) + __gitcomp " + deep shallow + " "" "${cur##--thread=}" + return + ;; --*) __gitcomp " - --stdout --attach --thread + --stdout --attach --no-attach --thread --thread= --output-directory --numbered --start-number --numbered-files --keep-subject --signoff - --in-reply-to= + --in-reply-to= --cc= --full-index --binary --not --all --cover-letter @@ -875,6 +995,21 @@ _git_format_patch () __git_complete_revlist } +_git_fsck () +{ + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp " + --tags --root --unreachable --cache --no-reflogs --full + --strict --verbose --lost-found + " + return + ;; + esac + COMPREPLY=() +} + _git_gc () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -997,6 +1132,7 @@ __git_log_shortlog_options=" " __git_log_pretty_formats="oneline short medium full fuller email raw format:" +__git_log_date_formats="relative iso8601 rfc2822 short local default raw" _git_log () { @@ -1014,10 +1150,13 @@ _git_log () " "" "${cur##--pretty=}" return ;; + --format=*) + __gitcomp "$__git_log_pretty_formats + " "" "${cur##--format=}" + return + ;; --date=*) - __gitcomp " - relative iso8601 rfc2822 short local default - " "" "${cur##--date=}" + __gitcomp "$__git_log_date_formats" "" "${cur##--date=}" return ;; --*) @@ -1029,7 +1168,7 @@ _git_log () --follow --abbrev-commit --abbrev= --relative-date --date= - --pretty= + --pretty= --format= --oneline --cherry-pick --graph --decorate @@ -1045,24 +1184,19 @@ _git_log () __git_complete_revlist } +__git_merge_options=" + --no-commit --no-stat --log --no-log --squash --strategy + --commit --stat --no-squash --ff --no-ff +" + _git_merge () { + __git_complete_strategy && return + local cur="${COMP_WORDS[COMP_CWORD]}" - case "${COMP_WORDS[COMP_CWORD-1]}" in - -s|--strategy) - __gitcomp "$(__git_merge_strategies)" - return - esac case "$cur" in - --strategy=*) - __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" - return - ;; --*) - __gitcomp " - --no-commit --no-stat --log --no-log --squash --strategy - --commit --stat --no-squash --ff --no-ff - " + __gitcomp "$__git_merge_options" return esac __gitcomp "$(__git_refs)" @@ -1073,10 +1207,7 @@ _git_mergetool () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --tool=*) - __gitcomp " - kdiff3 tkdiff meld xxdiff emerge - vimdiff gvimdiff ecmerge opendiff - " "" "${cur##--tool=}" + __gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}" return ;; --*) @@ -1111,40 +1242,44 @@ _git_name_rev () _git_pull () { - local cur="${COMP_WORDS[COMP_CWORD]}" + __git_complete_strategy && return - if [ "$COMP_CWORD" = 2 ]; then - __gitcomp "$(__git_remotes)" - else - __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" - fi + local cur="${COMP_WORDS[COMP_CWORD]}" + case "$cur" in + --*) + __gitcomp " + --rebase --no-rebase + $__git_merge_options + $__git_fetch_options + " + return + ;; + esac + __git_complete_remote_or_refspec } _git_push () { local cur="${COMP_WORDS[COMP_CWORD]}" - - if [ "$COMP_CWORD" = 2 ]; then + case "${COMP_WORDS[COMP_CWORD-1]}" in + --repo) __gitcomp "$(__git_remotes)" - else - case "$cur" in - *:*) - local pfx="" - case "$COMP_WORDBREAKS" in - *:*) : great ;; - *) pfx="${cur%%:*}:" ;; - esac - - __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" "$pfx" "${cur#*:}" - ;; - +*) - __gitcomp "$(__git_refs)" + "${cur#+}" - ;; - *) - __gitcomp "$(__git_refs)" - ;; - esac - fi + return + esac + case "$cur" in + --repo=*) + __gitcomp "$(__git_remotes)" "" "${cur##--repo=}" + return + ;; + --*) + __gitcomp " + --all --mirror --tags --dry-run --force --verbose + --receive-pack= --repo= + " + return + ;; + esac + __git_complete_remote_or_refspec } _git_rebase () @@ -1154,16 +1289,8 @@ _git_rebase () __gitcomp "--continue --skip --abort" return fi - case "${COMP_WORDS[COMP_CWORD-1]}" in - -s|--strategy) - __gitcomp "$(__git_merge_strategies)" - return - esac + __git_complete_strategy && return case "$cur" in - --strategy=*) - __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}" - return - ;; --*) __gitcomp "--onto --merge --strategy --interactive" return @@ -1171,18 +1298,39 @@ _git_rebase () __gitcomp "$(__git_refs)" } +__git_send_email_confirm_options="always never auto cc compose" +__git_send_email_suppresscc_options="author self cc ccbody sob cccmd body all" + _git_send_email () { local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in + --confirm=*) + __gitcomp " + $__git_send_email_confirm_options + " "" "${cur##--confirm=}" + return + ;; + --suppress-cc=*) + __gitcomp " + $__git_send_email_suppresscc_options + " "" "${cur##--suppress-cc=}" + + return + ;; + --smtp-encryption=*) + __gitcomp "ssl tls" "" "${cur##--smtp-encryption=}" + return + ;; --*) - __gitcomp "--bcc --cc --cc-cmd --chain-reply-to --compose - --dry-run --envelope-sender --from --identity + __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to + --compose --confirm= --dry-run --envelope-sender + --from --identity --in-reply-to --no-chain-reply-to --no-signed-off-by-cc --no-suppress-from --no-thread --quiet --signed-off-by-cc --smtp-pass --smtp-server - --smtp-server-port --smtp-ssl --smtp-user --subject - --suppress-cc --suppress-from --thread --to + --smtp-server-port --smtp-encryption= --smtp-user + --subject --suppress-cc= --suppress-from --thread --to --validate --no-validate" return ;; @@ -1190,6 +1338,35 @@ _git_send_email () COMPREPLY=() } +__git_config_get_set_variables () +{ + local prevword word config_file= c=$COMP_CWORD + while [ $c -gt 1 ]; do + word="${COMP_WORDS[c]}" + case "$word" in + --global|--system|--file=*) + config_file="$word" + break + ;; + -f|--file) + config_file="$word $prevword" + break + ;; + esac + prevword=$word + c=$((--c)) + done + + for i in $(git --git-dir="$(__gitdir)" config $config_file --list \ + 2>/dev/null); do + case "$i" in + *.*) + echo "${i/=*/}" + ;; + esac + done +} + _git_config () { local cur="${COMP_WORDS[COMP_CWORD]}" @@ -1221,7 +1398,8 @@ _git_config () __gitcomp "$(__git_merge_strategies)" return ;; - color.branch|color.diff|color.interactive|color.status|color.ui) + color.branch|color.diff|color.interactive|\ + color.showbranch|color.status|color.ui) __gitcomp "always never auto" return ;; @@ -1236,6 +1414,30 @@ _git_config () " return ;; + help.format) + __gitcomp "man info web html" + return + ;; + log.date) + __gitcomp "$__git_log_date_formats" + return + ;; + sendemail.aliasesfiletype) + __gitcomp "mutt mailrc pine elm gnus" + return + ;; + sendemail.confirm) + __gitcomp "$__git_send_email_confirm_options" + return + ;; + sendemail.suppresscc) + __gitcomp "$__git_send_email_suppresscc_options" + return + ;; + --get|--get-all|--unset|--unset-all) + __gitcomp "$(__git_config_get_set_variables)" + return + ;; *.*) COMPREPLY=() return @@ -1264,6 +1466,39 @@ _git_config () __gitcomp "$(__git_heads)" "$pfx" "$cur" "." return ;; + guitool.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp " + argprompt cmd confirm needsfile noconsole norescan + prompt revprompt revunmerged title + " "$pfx" "$cur" + return + ;; + difftool.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur" + return + ;; + man.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "cmd path" "$pfx" "$cur" + return + ;; + mergetool.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "cmd path trustExitCode" "$pfx" "$cur" + return + ;; + pager.*) + local pfx="${cur%.*}." + cur="${cur#*.}" + __gitcomp "$(__git_all_commands)" "$pfx" "$cur" + return + ;; remote.*.*) local pfx="${cur%.*}." cur="${cur##*.}" @@ -1279,8 +1514,15 @@ _git_config () __gitcomp "$(__git_remotes)" "$pfx" "$cur" "." return ;; + url.*.*) + local pfx="${cur%.*}." + cur="${cur##*.}" + __gitcomp "insteadof" "$pfx" "$cur" + return + ;; esac __gitcomp " + alias. apply.whitespace branch.autosetupmerge branch.autosetuprebase @@ -1298,11 +1540,15 @@ _git_config () color.diff.old color.diff.plain color.diff.whitespace + color.grep + color.grep.external + color.grep.match color.interactive color.interactive.header color.interactive.help color.interactive.prompt color.pager + color.showbranch color.status color.status.added color.status.changed @@ -1315,6 +1561,7 @@ _git_config () core.autocrlf core.bare core.compression + core.createObject core.deltaBaseCacheLimit core.editor core.excludesfile @@ -1345,11 +1592,21 @@ _git_config () diff.renameLimit diff.renameLimit. diff.renames + diff.suppressBlankEmpty + diff.tool + diff.wordRegex + difftool. + difftool.prompt fetch.unpackLimit + format.attach + format.cc format.headers format.numbered format.pretty + format.signoff + format.subjectprefix format.suffix + format.thread gc.aggressiveWindow gc.auto gc.autopacklimit @@ -1360,6 +1617,7 @@ _git_config () gc.rerereresolved gc.rerereunresolved gitcvs.allbinary + gitcvs.commitmsgannotation gitcvs.dbTableNamePrefix gitcvs.dbdriver gitcvs.dbname @@ -1368,6 +1626,7 @@ _git_config () gitcvs.enabled gitcvs.logfile gitcvs.usecrlfattr + guitool. gui.blamehistoryctx gui.commitmsgwidth gui.copyblamethreshold @@ -1394,13 +1653,24 @@ _git_config () http.sslVerify i18n.commitEncoding i18n.logOutputEncoding + imap.folder + imap.host + imap.pass + imap.port + imap.preformattedHTML + imap.sslverify + imap.tunnel + imap.user instaweb.browser instaweb.httpd instaweb.local instaweb.modulepath instaweb.port + interactive.singlekey log.date log.showroot + mailmap.file + man. man.viewer merge.conflictstyle merge.log @@ -1408,7 +1678,9 @@ _git_config () merge.stat merge.tool merge.verbosity + mergetool. mergetool.keepBackup + mergetool.prompt pack.compression pack.deltaCacheLimit pack.deltaCacheSize @@ -1418,8 +1690,11 @@ _git_config () pack.threads pack.window pack.windowMemory + pager. pull.octopus pull.twohead + push.default + rebase.stat receive.denyCurrentBranch receive.denyDeletes receive.denyNonFastForwards @@ -1428,11 +1703,32 @@ _git_config () repack.usedeltabaseoffset rerere.autoupdate rerere.enabled + sendemail.aliasesfile + sendemail.aliasesfiletype + sendemail.bcc + sendemail.cc + sendemail.cccmd + sendemail.chainreplyto + sendemail.confirm + sendemail.envelopesender + sendemail.multiedit + sendemail.signedoffbycc + sendemail.smtpencryption + sendemail.smtppass + sendemail.smtpserver + sendemail.smtpserverport + sendemail.smtpuser + sendemail.suppresscc + sendemail.suppressfrom + sendemail.thread + sendemail.to + sendemail.validate showbranch.default status.relativePaths status.showUntrackedFiles tar.umask transfer.unpackLimit + url. user.email user.name user.signingkey @@ -1443,7 +1739,7 @@ _git_config () _git_remote () { - local subcommands="add rename rm show prune update" + local subcommands="add rename rm show prune update set-head" local subcommand="$(__git_find_subcommand "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" @@ -1541,8 +1837,13 @@ _git_show () " "" "${cur##--pretty=}" return ;; + --format=*) + __gitcomp "$__git_log_pretty_formats + " "" "${cur##--format=}" + return + ;; --*) - __gitcomp "--pretty= + __gitcomp "--pretty= --format= --abbrev-commit --oneline $__git_diff_common_options " return @@ -1559,7 +1860,8 @@ _git_show_branch () __gitcomp " --all --remotes --topo-order --current --more= --list --independent --merge-base --no-name - --sha1-name --topics --reflog + --color --no-color + --sha1-name --sparse --topics --reflog " return ;; @@ -1579,10 +1881,10 @@ _git_stash () save,--*) __gitcomp "--keep-index" ;; - apply,--*) + apply,--*|pop,--*) __gitcomp "--index" ;; - show,--*|drop,--*|pop,--*|branch,--*) + show,--*|drop,--*|branch,--*) COMPREPLY=() ;; show,*|apply,*|drop,*|pop,*|branch,*) @@ -1771,6 +2073,7 @@ _git () --bare --version --exec-path + --html-path --work-tree= --help " @@ -1800,8 +2103,10 @@ _git () config) _git_config ;; describe) _git_describe ;; diff) _git_diff ;; + difftool) _git_difftool ;; fetch) _git_fetch ;; format-patch) _git_format_patch ;; + fsck) _git_fsck ;; gc) _git_gc ;; grep) _git_grep ;; help) _git_help ;; @@ -1841,7 +2146,7 @@ _gitk () __git_has_doubledash && return local cur="${COMP_WORDS[COMP_CWORD]}" - local g="$(git rev-parse --git-dir 2>/dev/null)" + local g="$(__gitdir)" local merge="" if [ -f "$g/MERGE_HEAD" ]; then merge="--merge" diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c index 90e7900e6d..f3b57bf1d2 100644 --- a/contrib/convert-objects/convert-objects.c +++ b/contrib/convert-objects/convert-objects.c @@ -59,7 +59,7 @@ static void convert_ascii_sha1(void *buffer) struct entry *entry; if (get_sha1_hex(buffer, sha1)) - die("expected sha1, got '%s'", (char*) buffer); + die("expected sha1, got '%s'", (char *) buffer); entry = convert_entry(sha1); memcpy(buffer, sha1_to_hex(entry->new_sha1), 40); } @@ -100,7 +100,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base if (!slash) { newlen += sprintf(new + newlen, "%o %s", mode, path); new[newlen++] = '\0'; - hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20); + hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20); newlen += 20; used += len; @@ -271,7 +271,7 @@ static void convert_commit(void *buffer, unsigned long size, unsigned char *resu unsigned long orig_size = size; if (memcmp(buffer, "tree ", 5)) - die("Bad commit '%s'", (char*) buffer); + die("Bad commit '%s'", (char *) buffer); convert_ascii_sha1((char *) buffer + 5); buffer = (char *) buffer + 46; /* "tree " + "hex sha1" + "\n" */ while (!memcmp(buffer, "parent ", 7)) { diff --git a/contrib/difftool/git-difftool b/contrib/difftool/git-difftool deleted file mode 100755 index 0cda3d2eea..0000000000 --- a/contrib/difftool/git-difftool +++ /dev/null @@ -1,73 +0,0 @@ -#!/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 deleted file mode 100755 index db3af6a833..0000000000 --- a/contrib/difftool/git-difftool-helper +++ /dev/null @@ -1,240 +0,0 @@ -#!/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/fast-import/git-p4 b/contrib/fast-import/git-p4 index 3832f60225..342529db30 100755 --- a/contrib/fast-import/git-p4 +++ b/contrib/fast-import/git-p4 @@ -1142,7 +1142,7 @@ class P4Sync(Command): s = '' for (key, val) in self.users.items(): - s += "%s\t%s\n" % (key, val) + s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1)) open(self.getUserCacheFilename(), "wb").write(s) self.userMapFromPerforceServer = True diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl index 23aeb257b9..78e40d2a13 100755 --- a/contrib/fast-import/import-tars.perl +++ b/contrib/fast-import/import-tars.perl @@ -14,13 +14,18 @@ die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV; my $branch_name = 'import-tars'; my $branch_ref = "refs/heads/$branch_name"; -my $committer_name = 'T Ar Creator'; -my $committer_email = 'tar@example.com'; +my $author_name = $ENV{'GIT_AUTHOR_NAME'} || 'T Ar Creator'; +my $author_email = $ENV{'GIT_AUTHOR_EMAIL'} || 'tar@example.com'; +my $committer_name = $ENV{'GIT_COMMITTER_NAME'} || `git config --get user.name`; +my $committer_email = $ENV{'GIT_COMMITTER_EMAIL'} || `git config --get user.email`; + +chomp($committer_name, $committer_email); open(FI, '|-', 'git', 'fast-import', '--quiet') or die "Unable to start git fast-import: $!\n"; foreach my $tar_file (@ARGV) { + my $commit_time = time; $tar_file =~ m,([^/]+)$,; my $tar_name = $1; @@ -39,7 +44,7 @@ foreach my $tar_file (@ARGV) die "Unrecognized compression format: $tar_file\n"; } - my $commit_time = 0; + my $author_time = 0; my $next_mark = 1; my $have_top_dir = 1; my ($top_dir, %files); @@ -77,10 +82,16 @@ foreach my $tar_file (@ARGV) $mtime = oct $mtime; next if $typeflag == 5; # directory - print FI "blob\n", "mark :$next_mark\n", "data $size\n"; - while ($size > 0 && read(I, $_, 512) == 512) { - print FI substr($_, 0, $size); - $size -= 512; + print FI "blob\n", "mark :$next_mark\n"; + if ($typeflag == 2) { # symbolic link + print FI "data ", length($linkname), "\n", $linkname; + $mode = 0120000; + } else { + print FI "data $size\n"; + while ($size > 0 && read(I, $_, 512) == 512) { + print FI substr($_, 0, $size); + $size -= 512; + } } print FI "\n"; @@ -92,7 +103,7 @@ foreach my $tar_file (@ARGV) } $files{$path} = [$next_mark++, $mode]; - $commit_time = $mtime if $mtime > $commit_time; + $author_time = $mtime if $mtime > $author_time; $path =~ m,^([^/]+)/,; $top_dir = $1 unless $top_dir; $have_top_dir = 0 if $top_dir ne $1; @@ -100,6 +111,7 @@ foreach my $tar_file (@ARGV) print FI <<EOF; commit $branch_ref +author $author_name <$author_email> $author_time +0000 committer $committer_name <$committer_email> $commit_time +0000 data <<END_OF_COMMIT_MESSAGE Imported from $tar_file. @@ -112,14 +124,15 @@ EOF { my ($mark, $mode) = @{$files{$path}}; $path =~ s,^([^/]+)/,, if $have_top_dir; - printf FI "M %o :%i %s\n", $mode & 0111 ? 0755 : 0644, $mark, $path; + $mode = $mode & 0111 ? 0755 : 0644 unless $mode == 0120000; + printf FI "M %o :%i %s\n", $mode, $mark, $path; } print FI "\n"; print FI <<EOF; tag $tar_name from $branch_ref -tagger $committer_name <$committer_email> $commit_time +0000 +tagger $author_name <$author_email> $author_time +0000 data <<END_OF_TAG_MESSAGE Package $tar_name END_OF_TAG_MESSAGE diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email index 60cbab65d3..2a66063e44 100644..100755 --- a/contrib/hooks/post-receive-email +++ b/contrib/hooks/post-receive-email @@ -44,6 +44,10 @@ # --pretty %s", displaying the commit id, author, date and log # message. To list full patches separated by a blank line, you # could set this to "git show -C %s; echo". +# To list a gitweb/cgit URL *and* a full patch for each change set, use this: +# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo" +# Be careful if "..." contains things that will be expanded by shell "eval" +# or printf. # # Notes # ----- diff --git a/contrib/thunderbird-patch-inline/README b/contrib/thunderbird-patch-inline/README index 39f96aa115..000147bbe4 100644 --- a/contrib/thunderbird-patch-inline/README +++ b/contrib/thunderbird-patch-inline/README @@ -1,12 +1,12 @@ appp.sh is a script that is supposed to be used together with ExternalEditor -for Mozilla Thundebird. It will let you include patches inline in e-mails +for Mozilla Thunderbird. It will let you include patches inline in e-mails in an easy way. Usage: - Generate the patch with git format-patch. - Start writing a new e-mail in Thunderbird. - Press the external editor button (or Ctrl-E) to run appp.sh -- Select the previosly generated patch file. +- Select the previously generated patch file. - Finish editing the e-mail. Any text that is entered into the message editor before appp.sh is called @@ -10,7 +10,7 @@ enum { A = GIT_ALPHA, D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ - R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | * */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ }; unsigned char sane_ctype[256] = { @@ -229,7 +229,7 @@ static char *path_ok(char *directory) } if (!path) { - logerror("'%s': unable to chdir or not a git archive", dir); + logerror("'%s' does not appear to be a git repository", dir); return NULL; } @@ -406,15 +406,15 @@ static char *xstrdup_tolower(const char *str) } /* - * Separate the "extra args" information as supplied by the client connection. + * Read the host as supplied by the client connection. */ -static void parse_extra_args(char *extra_args, int buflen) +static void parse_host_arg(char *extra_args, int buflen) { char *val; int vallen; char *end = extra_args + buflen; - while (extra_args < end && *extra_args) { + if (extra_args < end && *extra_args) { saw_extended_args = 1; if (strncasecmp("host=", extra_args, 5) == 0) { val = extra_args + 5; @@ -436,6 +436,8 @@ static void parse_extra_args(char *extra_args, int buflen) /* On to the next one */ extra_args = val + vallen; } + if (extra_args < end && *extra_args) + die("Invalid request"); } /* @@ -444,27 +446,27 @@ static void parse_extra_args(char *extra_args, int buflen) if (hostname) { #ifndef NO_IPV6 struct addrinfo hints; - struct addrinfo *ai, *ai0; + struct addrinfo *ai; int gai; static char addrbuf[HOST_NAME_MAX + 1]; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; - gai = getaddrinfo(hostname, 0, &hints, &ai0); + gai = getaddrinfo(hostname, 0, &hints, &ai); if (!gai) { - for (ai = ai0; ai; ai = ai->ai_next) { - struct sockaddr_in *sin_addr = (void *)ai->ai_addr; - - inet_ntop(AF_INET, &sin_addr->sin_addr, - addrbuf, sizeof(addrbuf)); - free(canon_hostname); - canon_hostname = xstrdup(ai->ai_canonname); - free(ip_address); - ip_address = xstrdup(addrbuf); - break; - } - freeaddrinfo(ai0); + struct sockaddr_in *sin_addr = (void *)ai->ai_addr; + + inet_ntop(AF_INET, &sin_addr->sin_addr, + addrbuf, sizeof(addrbuf)); + free(ip_address); + ip_address = xstrdup(addrbuf); + + free(canon_hostname); + canon_hostname = xstrdup(ai->ai_canonname ? + ai->ai_canonname : ip_address); + + freeaddrinfo(ai); } #else struct hostent *hent; @@ -545,7 +547,7 @@ static int execute(struct sockaddr *addr) hostname = canon_hostname = ip_address = tcp_port = NULL; if (len != pktlen) - parse_extra_args(line + len + 1, pktlen - len - 1); + parse_host_arg(line + len + 1, pktlen - len - 1); for (i = 0; i < ARRAY_SIZE(daemon_service); i++) { struct daemon_service *s = &(daemon_service[i]); diff --git a/decorate.c b/decorate.c index 82d9e221ea..2f8a63e388 100644 --- a/decorate.c +++ b/decorate.c @@ -8,7 +8,9 @@ static unsigned int hash_obj(const struct object *obj, unsigned int n) { - unsigned int hash = *(unsigned int *)obj->sha1; + unsigned int hash; + + memcpy(&hash, obj->sha1, sizeof(unsigned int)); return hash % n; } @@ -16,7 +18,7 @@ static void *insert_decoration(struct decoration *n, const struct object *base, { int size = n->size; struct object_decoration *hash = n->hash; - int j = hash_obj(base, size); + unsigned int j = hash_obj(base, size); while (hash[j].base) { if (hash[j].base == base) { @@ -68,7 +70,7 @@ void *add_decoration(struct decoration *n, const struct object *obj, /* Lookup a decoration pointer */ void *lookup_decoration(struct decoration *n, const struct object *obj) { - int j; + unsigned int j; /* nothing to lookup */ if (!n->size) @@ -90,12 +90,11 @@ static inline unsigned long get_delta_hdr_size(const unsigned char **datap, const unsigned char *top) { const unsigned char *data = *datap; - unsigned char cmd; - unsigned long size = 0; + unsigned long cmd, size = 0; int i = 0; do { cmd = *data++; - size |= (cmd & ~0x80) << i; + size |= (cmd & 0x7f) << i; i += 7; } while (cmd & 0x80 && data < top); *datap = data; diff --git a/diff-lib.c b/diff-lib.c index 79d0606834..0aba6cda3c 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -31,7 +31,7 @@ static int check_removed(const struct cache_entry *ce, struct stat *st) return -1; return 1; } - if (has_symlink_leading_path(ce_namelen(ce), ce->name)) + if (has_symlink_leading_path(ce->name, ce_namelen(ce))) return 1; if (S_ISDIR(st->st_mode)) { unsigned char sub[20]; @@ -214,7 +214,7 @@ static int get_stat_data(struct cache_entry *ce, const unsigned char *sha1 = ce->sha1; unsigned int mode = ce->ce_mode; - if (!cached) { + if (!cached && !ce_uptodate(ce)) { int changed; struct stat st; changed = check_removed(ce, &st); diff --git a/diff-no-index.c b/diff-no-index.c index 7273a7a0b6..4ebc1dbd87 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -38,6 +38,10 @@ static int get_mode(const char *path, int *mode) if (!path || !strcmp(path, "/dev/null")) *mode = 0; +#ifdef _WIN32 + else if (!strcasecmp(path, "nul")) + *mode = 0; +#endif else if (!strcmp(path, "-")) *mode = create_ce_mode(0666); else if (lstat(path, &st)) @@ -229,7 +233,7 @@ void diff_no_index(struct rev_info *revs, if (prefix) { int len = strlen(prefix); - revs->diffopt.paths = xcalloc(2, sizeof(char*)); + revs->diffopt.paths = xcalloc(2, sizeof(char *)); for (i = 0; i < 2; i++) { const char *p = argv[argc - 2 + i]; /* @@ -30,14 +30,14 @@ int diff_auto_refresh_index = 1; static int diff_mnemonic_prefix; static char diff_colors[][COLOR_MAXLEN] = { - "\033[m", /* reset */ - "", /* PLAIN (normal) */ - "\033[1m", /* METAINFO (bold) */ - "\033[36m", /* FRAGINFO (cyan) */ - "\033[31m", /* OLD (red) */ - "\033[32m", /* NEW (green) */ - "\033[33m", /* COMMIT (yellow) */ - "\033[41m", /* WHITESPACE (red background) */ + GIT_COLOR_RESET, + GIT_COLOR_NORMAL, /* PLAIN */ + GIT_COLOR_BOLD, /* METAINFO */ + GIT_COLOR_CYAN, /* FRAGINFO */ + GIT_COLOR_RED, /* OLD */ + GIT_COLOR_GREEN, /* NEW */ + GIT_COLOR_YELLOW, /* COMMIT */ + GIT_COLOR_BG_RED, /* WHITESPACE */ }; static void diff_filespec_load_driver(struct diff_filespec *one); @@ -62,6 +62,15 @@ static int parse_diff_color_slot(const char *var, int ofs) die("bad config variable '%s'", var); } +static int git_config_rename(const char *var, const char *value) +{ + if (!value) + return DIFF_DETECT_RENAME; + if (!strcasecmp(value, "copies") || !strcasecmp(value, "copy")) + return DIFF_DETECT_COPY; + return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0; +} + /* * These are to give UI layer defaults. * The core-level commands such as git-diff-files should @@ -75,13 +84,7 @@ int git_diff_ui_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "diff.renames")) { - if (!value) - diff_detect_rename_default = DIFF_DETECT_RENAME; - else if (!strcasecmp(value, "copies") || - !strcasecmp(value, "copy")) - diff_detect_rename_default = DIFF_DETECT_COPY; - else if (git_config_bool(var,value)) - diff_detect_rename_default = DIFF_DETECT_RENAME; + diff_detect_rename_default = git_config_rename(var, value); return 0; } if (!strcmp(var, "diff.autorefreshindex")) { @@ -186,7 +189,7 @@ 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); + unlink_or_warn(diff_temp[i].name); diff_temp[i].name = NULL; } } @@ -836,10 +839,9 @@ static int scale_linear(int it, int width, int max_change) } static void show_name(FILE *file, - const char *prefix, const char *name, int len, - const char *reset, const char *set) + const char *prefix, const char *name, int len) { - fprintf(file, " %s%s%-*s%s |", set, prefix, len, name, reset); + fprintf(file, " %s%-*s |", prefix, len, name); } static void show_graph(FILE *file, char ch, int cnt, const char *set, const char *reset) @@ -873,9 +875,9 @@ static void fill_print_name(struct diffstat_file *file) file->print_name = pname; } -static void show_stats(struct diffstat_t* data, struct diff_options *options) +static void show_stats(struct diffstat_t *data, struct diff_options *options) { - int i, len, add, del, total, adds = 0, dels = 0; + int i, len, add, del, adds = 0, dels = 0; int max_change = 0, max_len = 0; int total_files = data->nr; int width, name_width; @@ -953,7 +955,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) } if (data->files[i]->is_binary) { - show_name(options->file, prefix, name, len, reset, set); + show_name(options->file, prefix, name, len); fprintf(options->file, " Bin "); fprintf(options->file, "%s%d%s", del_c, deleted, reset); fprintf(options->file, " -> "); @@ -963,7 +965,7 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) continue; } else if (data->files[i]->is_unmerged) { - show_name(options->file, prefix, name, len, reset, set); + show_name(options->file, prefix, name, len); fprintf(options->file, " Unmerged\n"); continue; } @@ -978,16 +980,14 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) */ add = added; del = deleted; - total = add + del; adds += add; dels += del; if (width <= max_change) { add = scale_linear(add, width, max_change); del = scale_linear(del, width, max_change); - total = add + del; } - show_name(options->file, prefix, name, len, reset, set); + show_name(options->file, prefix, name, len); fprintf(options->file, "%5d%s", added + deleted, added + deleted ? " " : ""); show_graph(options->file, '+', add, add_c, reset); @@ -995,8 +995,8 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options) fprintf(options->file, "\n"); } fprintf(options->file, - "%s %d files changed, %d insertions(+), %d deletions(-)%s\n", - set, total_files, adds, dels, reset); + " %d files changed, %d insertions(+), %d deletions(-)\n", + total_files, adds, dels); } static void show_shortstats(struct diffstat_t* data, struct diff_options *options) @@ -1024,7 +1024,7 @@ static void show_shortstats(struct diffstat_t* data, struct diff_options *option total_files, adds, dels); } -static void show_numstat(struct diffstat_t* data, struct diff_options *options) +static void show_numstat(struct diffstat_t *data, struct diff_options *options) { int i; @@ -1964,8 +1964,16 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, { int fd; struct strbuf buf = STRBUF_INIT; + struct strbuf template = STRBUF_INIT; + char *path_dup = xstrdup(path); + const char *base = basename(path_dup); - fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX"); + /* Generate "XXXXXX_basename.ext" */ + strbuf_addstr(&template, "XXXXXX_"); + strbuf_addstr(&template, base); + + fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf, + strlen(base) + 1); if (fd < 0) die("unable to create temp-file: %s", strerror(errno)); if (convert_to_working_tree(path, @@ -1981,6 +1989,8 @@ static void prep_temp_blob(const char *path, struct diff_tempfile *temp, temp->hex[40] = 0; sprintf(temp->mode, "%06o", mode); strbuf_release(&buf); + strbuf_release(&template); + free(path_dup); } static struct diff_tempfile *prepare_temp_file(const char *name, @@ -2014,18 +2024,15 @@ static struct diff_tempfile *prepare_temp_file(const char *name, die("stat(%s): %s", name, strerror(errno)); } if (S_ISLNK(st.st_mode)) { - int ret; - char buf[PATH_MAX + 1]; /* ought to be SYMLINK_MAX */ - ret = readlink(name, buf, sizeof(buf)); - if (ret < 0) + struct strbuf sb = STRBUF_INIT; + if (strbuf_readlink(&sb, name, st.st_size) < 0) die("readlink(%s)", name); - if (ret == sizeof(buf)) - die("symlink too long: %s", name); - prep_temp_blob(name, temp, buf, ret, + prep_temp_blob(name, temp, sb.buf, sb.len, (one->sha1_valid ? one->sha1 : null_sha1), (one->sha1_valid ? one->mode : S_IFLNK)); + strbuf_release(&sb); } else { /* we can borrow from the file in the work tree */ @@ -2582,13 +2589,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) /* xdiff options */ else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space")) - options->xdl_opts |= XDF_IGNORE_WHITESPACE; + DIFF_XDL_SET(options, IGNORE_WHITESPACE); else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) - options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; + DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE); else if (!strcmp(arg, "--ignore-space-at-eol")) - options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL; + DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL); else if (!strcmp(arg, "--patience")) - options->xdl_opts |= XDF_PATIENCE_DIFF; + DIFF_XDL_SET(options, PATIENCE_DIFF); /* flags options */ else if (!strcmp(arg, "--binary")) { @@ -2609,10 +2616,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) DIFF_OPT_SET(options, COLOR_DIFF); else if (!strcmp(arg, "--no-color")) 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 (!strcmp(arg, "--color-words")) { + DIFF_OPT_SET(options, COLOR_DIFF); + DIFF_OPT_SET(options, COLOR_DIFF_WORDS); + } else if (!prefixcmp(arg, "--color-words=")) { - options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS; + DIFF_OPT_SET(options, COLOR_DIFF); + DIFF_OPT_SET(options, COLOR_DIFF_WORDS); options->word_regex = arg + 14; } else if (!strcmp(arg, "--exit-code")) @@ -3586,6 +3596,7 @@ 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) { + strbuf_release(&buf); remove_tempfile(); error("error running textconv command '%s'", pgm); return NULL; @@ -69,6 +69,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q, #define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag) #define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag) #define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag) +#define DIFF_XDL_TST(opts, flag) ((opts)->xdl_opts & XDF_##flag) +#define DIFF_XDL_SET(opts, flag) ((opts)->xdl_opts |= XDF_##flag) +#define DIFF_XDL_CLR(opts, flag) ((opts)->xdl_opts &= ~XDF_##flag) struct diff_options { const char *filter; diff --git a/diffcore-break.c b/diffcore-break.c index 31cdcfe8bc..d7097bb576 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -45,7 +45,7 @@ static int should_break(struct diff_filespec *src, * The value we return is 1 if we want the pair to be broken, * or 0 if we do not. */ - unsigned long delta_size, base_size, max_size; + unsigned long delta_size, max_size; unsigned long src_copied, literal_added, src_removed; *merge_score_p = 0; /* assume no deletion --- "do not break" @@ -64,7 +64,6 @@ static int should_break(struct diff_filespec *src, if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) return 0; /* error but caught downstream */ - base_size = ((src->size < dst->size) ? src->size : dst->size); max_size = ((src->size > dst->size) ? src->size : dst->size); if (max_size < MINIMUM_BREAK_SIZE) return 0; /* we do not break too small filepair */ diff --git a/diffcore-rename.c b/diffcore-rename.c index 0b0d6b8c8c..63ac998bfa 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -267,7 +267,7 @@ static int find_identical_files(struct file_similarity *src, int score; struct diff_filespec *source = p->filespec; - /* False hash collission? */ + /* False hash collision? */ if (hashcmp(source->sha1, target->sha1)) continue; /* Non-regular files? If so, the modes must match! */ @@ -156,7 +156,7 @@ void add_exclude(const char *string, const char *base, if (len && string[len - 1] == '/') { char *s; x = xmalloc(sizeof(*x) + len); - s = (char*)(x+1); + s = (char *)(x+1); memcpy(s, string, len - 1); s[len - 1] = '\0'; string = s; @@ -396,7 +396,7 @@ static struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathna static struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len) { - if (cache_name_pos(pathname, len) >= 0) + if (!cache_name_is_other(pathname, len)) return NULL; ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc); @@ -487,14 +487,14 @@ static enum directory_treatment treat_directory(struct dir_struct *dir, return recurse_into_directory; case index_gitdir: - if (dir->show_other_directories) + if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES) return ignore_directory; return show_directory; case index_nonexistent: - if (dir->show_other_directories) + if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES) break; - if (!dir->no_gitlinks) { + if (!(dir->flags & DIR_NO_GITLINKS)) { unsigned char sha1[20]; if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0) return show_directory; @@ -503,7 +503,7 @@ static enum directory_treatment treat_directory(struct dir_struct *dir, } /* This is the "show_other_directories" case */ - if (!dir->hide_empty_directories) + if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES)) return show_directory; if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify)) return ignore_directory; @@ -576,7 +576,7 @@ static int get_dtype(struct dirent *de, const char *path) */ static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify) { - DIR *fdir = opendir(path); + DIR *fdir = opendir(*path ? path : "."); int contents = 0; if (fdir) { @@ -601,7 +601,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co dtype = DTYPE(de); exclude = excluded(dir, fullname, &dtype); - if (exclude && dir->collect_ignored + if (exclude && (dir->flags & DIR_COLLECT_IGNORED) && in_pathspec(fullname, baselen + len, simplify)) dir_add_ignored(dir, fullname, baselen + len); @@ -609,7 +609,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co * Excluded? If we don't explicitly want to show * ignored files, ignore it */ - if (exclude && !dir->show_ignored) + if (exclude && !(dir->flags & DIR_SHOW_IGNORED)) continue; if (dtype == DT_UNKNOWN) @@ -621,7 +621,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co * even if we don't ignore them, since the * directory may contain files that we do.. */ - if (!exclude && dir->show_ignored) { + if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) { if (dtype != DT_DIR) continue; } @@ -634,7 +634,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co len++; switch (treat_directory(dir, fullname, baselen + len, simplify)) { case show_directory: - if (exclude != dir->show_ignored) + if (exclude != !!(dir->flags + & DIR_SHOW_IGNORED)) continue; break; case recurse_into_directory: @@ -720,7 +721,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i { struct path_simplify *simplify; - if (has_symlink_leading_path(strlen(path), path)) + if (has_symlink_leading_path(path, strlen(path))) return dir->nr; simplify = create_simplify(pathspec); @@ -34,11 +34,13 @@ struct exclude_stack { struct dir_struct { int nr, alloc; int ignored_nr, ignored_alloc; - unsigned int show_ignored:1, - show_other_directories:1, - hide_empty_directories:1, - no_gitlinks:1, - collect_ignored:1; + enum { + DIR_SHOW_IGNORED = 1<<0, + DIR_SHOW_OTHER_DIRECTORIES = 1<<1, + DIR_HIDE_EMPTY_DIRECTORIES = 1<<2, + DIR_NO_GITLINKS = 1<<3, + DIR_COLLECT_IGNORED = 1<<4 + } flags; struct dir_entry **entries; struct dir_entry **ignored; @@ -2,15 +2,19 @@ #include "blob.h" #include "dir.h" -static void create_directories(const char *path, const struct checkout *state) +static void create_directories(const char *path, int path_len, + const struct checkout *state) { - int len = strlen(path); - char *buf = xmalloc(len + 1); - const char *slash = path; - - while ((slash = strchr(slash+1, '/')) != NULL) { - len = slash - path; - memcpy(buf, path, len); + char *buf = xmalloc(path_len + 1); + int len = 0; + + while (len < path_len) { + do { + buf[len] = path[len]; + len++; + } while (len < path_len && path[len] != '/'); + if (len >= path_len) + break; buf[len] = 0; /* @@ -20,7 +24,7 @@ static void create_directories(const char *path, const struct checkout *state) * 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)) + if (has_dirs_only_path(buf, len, state->base_dir_len)) continue; /* ok, it is already a directory. */ /* @@ -31,7 +35,7 @@ static void create_directories(const char *path, const struct checkout *state) */ if (mkdir(buf, 0777)) { if (errno == EEXIST && state->force && - !unlink(buf) && !mkdir(buf, 0777)) + !unlink_or_warn(buf) && !mkdir(buf, 0777)) continue; die("cannot create directory at %s", buf); } @@ -74,7 +78,7 @@ static int create_file(const char *path, unsigned int mode) return open(path, O_WRONLY | O_CREAT | O_EXCL, mode); } -static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size) +static void *read_blob_entry(struct cache_entry *ce, unsigned long *size) { enum object_type type; void *new = read_sha1_file(ce->sha1, &type, size); @@ -89,36 +93,52 @@ static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile) { - int fd; - long wrote; - - switch (ce->ce_mode & S_IFMT) { - char *new; - struct strbuf buf; - unsigned long size; + unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT; + int fd, ret, fstat_done = 0; + char *new; + struct strbuf buf = STRBUF_INIT; + unsigned long size; + size_t wrote, newsize = 0; + struct stat st; + switch (ce_mode_s_ifmt) { case S_IFREG: - new = read_blob_entry(ce, path, &size); + case S_IFLNK: + new = read_blob_entry(ce, &size); if (!new) return error("git checkout-index: unable to read sha1 file of %s (%s)", path, sha1_to_hex(ce->sha1)); + if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) { + ret = symlink(new, path); + free(new); + if (ret) + return error("git checkout-index: unable to create symlink %s (%s)", + path, strerror(errno)); + break; + } + /* * Convert from git internal format to working tree format */ - strbuf_init(&buf, 0); - if (convert_to_working_tree(ce->name, new, size, &buf)) { - size_t newsize = 0; + if (ce_mode_s_ifmt == S_IFREG && + convert_to_working_tree(ce->name, new, size, &buf)) { free(new); new = strbuf_detach(&buf, &newsize); size = newsize; } if (to_tempfile) { - strcpy(path, ".merge_file_XXXXXX"); + if (ce_mode_s_ifmt == S_IFREG) + strcpy(path, ".merge_file_XXXXXX"); + else + strcpy(path, ".merge_link_XXXXXX"); fd = mkstemp(path); - } else + } else if (ce_mode_s_ifmt == S_IFREG) { fd = create_file(path, ce->ce_mode); + } else { + fd = create_file(path, 0666); + } if (fd < 0) { free(new); return error("git checkout-index: unable to create file %s (%s)", @@ -126,41 +146,17 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout } wrote = write_in_full(fd, new, size); + /* use fstat() only when path == ce->name */ + if (fstat_is_reliable() && + state->refresh_cache && !to_tempfile && !state->base_dir_len) { + fstat(fd, &st); + fstat_done = 1; + } close(fd); free(new); if (wrote != size) return error("git checkout-index: unable to write file %s", path); break; - case S_IFLNK: - new = read_blob_entry(ce, path, &size); - if (!new) - return error("git checkout-index: unable to read sha1 file of %s (%s)", - path, sha1_to_hex(ce->sha1)); - if (to_tempfile || !has_symlinks) { - if (to_tempfile) { - strcpy(path, ".merge_link_XXXXXX"); - fd = mkstemp(path); - } else - fd = create_file(path, 0666); - if (fd < 0) { - free(new); - return error("git checkout-index: unable to create " - "file %s (%s)", path, strerror(errno)); - } - wrote = write_in_full(fd, new, size); - close(fd); - free(new); - if (wrote != size) - return error("git checkout-index: unable to write file %s", - path); - } else { - wrote = symlink(new, path); - free(new); - if (wrote) - return error("git checkout-index: unable to create " - "symlink %s (%s)", path, strerror(errno)); - } - break; case S_IFGITLINK: if (to_tempfile) return error("git checkout-index: cannot create temporary subproject %s", path); @@ -172,8 +168,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout } if (state->refresh_cache) { - struct stat st; - lstat(ce->name, &st); + if (!fstat_done) + lstat(ce->name, &st); fill_stat_cache_info(ce, &st); } return 0; @@ -190,6 +186,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t memcpy(path, state->base_dir, len); strcpy(path + len, ce->name); + len += ce_namelen(ce); if (!lstat(path, &st)) { unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID); @@ -218,6 +215,6 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t return error("unable to unlink old '%s' (%s)", path, strerror(errno)); } else if (state->not_new) return 0; - create_directories(path, state); + create_directories(path, len, state); return write_entry(ce, path, state, 0); } diff --git a/environment.c b/environment.c index e278bce0ea..801a005ef1 100644 --- a/environment.c +++ b/environment.c @@ -42,6 +42,11 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; +enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; +#ifndef OBJECT_CREATION_MODE +#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS +#endif +enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; /* Parallel index stat data preload? */ int core_preload_index = 0; diff --git a/fast-import.c b/fast-import.c index d5fc042bbf..a2a24588a9 100644 --- a/fast-import.c +++ b/fast-import.c @@ -1,4 +1,5 @@ /* +(See Documentation/git-fast-import.txt for maintained documentation.) Format of STDIN stream: stream ::= cmd*; @@ -18,8 +19,8 @@ Format of STDIN stream: new_commit ::= 'commit' sp ref_str lf mark? - ('author' sp name '<' email '>' when lf)? - 'committer' sp name '<' email '>' when lf + ('author' sp name sp '<' email '>' sp when lf)? + 'committer' sp name sp '<' email '>' sp when lf commit_msg ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* @@ -43,7 +44,7 @@ Format of STDIN stream: new_tag ::= 'tag' sp tag_str lf 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf - ('tagger' sp name '<' email '>' when lf)? + ('tagger' sp name sp '<' email '>' sp when lf)? tag_msg; tag_msg ::= data; @@ -75,7 +76,7 @@ Format of STDIN stream: delim lf; # note: declen indicates the length of binary_data in bytes. - # declen does not include the lf preceeding the binary data. + # declen does not include the lf preceding the binary data. # exact_data ::= 'data' sp declen lf binary_data; @@ -132,8 +133,8 @@ Format of STDIN stream: # always escapes the related input from comment processing. # # In case it is not clear, the '#' that starts the comment - # must be the first character on that the line (an lf have - # preceeded it). + # must be the first character on that line (an lf + # preceded it). # comment ::= '#' not_lf* lf; not_lf ::= # Any byte that is not ASCII newline (LF); @@ -211,7 +212,7 @@ struct tree_content; struct tree_entry { struct tree_content *tree; - struct atom_str* name; + struct atom_str *name; struct tree_entry_ms { uint16_t mode; @@ -312,7 +313,7 @@ static unsigned int object_entry_alloc = 5000; static struct object_entry_pool *blocks; static struct object_entry *object_table[1 << 16]; static struct mark_set *marks; -static const char* mark_file; +static const char *mark_file; /* Our last blob */ static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 }; @@ -671,7 +672,7 @@ static struct branch *lookup_branch(const char *name) static struct branch *new_branch(const char *name) { unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; - struct branch* b = lookup_branch(name); + struct branch *b = lookup_branch(name); if (b) die("Invalid attempt to create duplicate branch: %s", name); @@ -930,7 +931,7 @@ static void unkeep_all_packs(void) struct packed_git *p = all_packs[k]; snprintf(name, sizeof(name), "%s/pack/pack-%s.keep", get_object_directory(), sha1_to_hex(p->sha1)); - unlink(name); + unlink_or_warn(name); } } @@ -952,7 +953,7 @@ static void end_packfile(void) close(pack_data->pack_fd); idx_name = keep_pack(create_index()); - /* Register the packfile with core git's machinary. */ + /* Register the packfile with core git's machinery. */ new_p = add_packed_git(idx_name, strlen(idx_name), 1); if (!new_p) die("core git rejected index %s", idx_name); @@ -980,7 +981,7 @@ static void end_packfile(void) } else { close(old_p->pack_fd); - unlink(old_p->pack_name); + unlink_or_warn(old_p->pack_name); } free(old_p); @@ -1034,7 +1035,7 @@ static int store_object( git_SHA_CTX c; z_stream s; - hdrlen = sprintf((char*)hdr,"%s %lu", typename(type), + hdrlen = sprintf((char *)hdr,"%s %lu", typename(type), (unsigned long)dat->len) + 1; git_SHA1_Init(&c); git_SHA1_Update(&c, hdr, hdrlen); @@ -1216,7 +1217,7 @@ static const char *get_mode(const char *str, uint16_t *modep) static void load_tree(struct tree_entry *root) { - unsigned char* sha1 = root->versions[1].sha1; + unsigned char *sha1 = root->versions[1].sha1; struct object_entry *myoe; struct tree_content *t; unsigned long size; @@ -1257,8 +1258,8 @@ static void load_tree(struct tree_entry *root) e->versions[0].mode = e->versions[1].mode; e->name = to_atom(c, strlen(c)); c += e->name->str_len + 1; - hashcpy(e->versions[0].sha1, (unsigned char*)c); - hashcpy(e->versions[1].sha1, (unsigned char*)c); + hashcpy(e->versions[0].sha1, (unsigned char *)c); + hashcpy(e->versions[1].sha1, (unsigned char *)c); c += 20; } free(buf); @@ -1742,21 +1743,19 @@ static void parse_data(struct strbuf *sb) static int validate_raw_date(const char *src, char *result, int maxlen) { const char *orig_src = src; - char *endp, sign; - unsigned long date; + char *endp; errno = 0; - date = strtoul(src, &endp, 10); + strtoul(src, &endp, 10); if (errno || endp == src || *endp != ' ') return -1; src = endp + 1; if (*src != '-' && *src != '+') return -1; - sign = *src; - date = strtoul(src + 1, &endp, 10); + strtoul(src + 1, &endp, 10); if (errno || endp == src || *endp || (endp - orig_src) >= maxlen) return -1; @@ -148,20 +148,17 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) struct tree_desc desc; unsigned o_mode; const char *o_name; - const unsigned char *o_sha1; init_tree_desc(&desc, item->buffer, item->size); o_mode = 0; o_name = NULL; - o_sha1 = NULL; while (desc.size) { unsigned mode; const char *name; - const unsigned char *sha1; - sha1 = tree_entry_extract(&desc, &name, &mode); + tree_entry_extract(&desc, &name, &mode); if (strchr(name, '/')) has_full_path = 1; @@ -207,7 +204,6 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func) o_mode = mode; o_name = name; - o_sha1 = sha1; } retval = 0; @@ -23,7 +23,7 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...); * the return value is: * -1 error in processing the object * <0 return value of the callback, which lead to an abort - * >0 return value of the first sigaled error >0 (in the case of no other errors) + * >0 return value of the first signaled error >0 (in the case of no other errors) * 0 everything OK */ int fsck_walk(struct object *obj, fsck_walk_func walk, void *data); diff --git a/git-add--interactive.perl b/git-add--interactive.perl index 5407b2e1b8..df9f231635 100755 --- a/git-add--interactive.perl +++ b/git-add--interactive.perl @@ -767,6 +767,96 @@ sub split_hunk { return @split; } +sub find_last_o_ctx { + my ($it) = @_; + my $text = $it->{TEXT}; + my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]); + my $i = @{$text}; + my $last_o_ctx = $o_ofs + $o_cnt; + while (0 < --$i) { + my $line = $text->[$i]; + if ($line =~ /^ /) { + $last_o_ctx--; + next; + } + last; + } + return $last_o_ctx; +} + +sub merge_hunk { + my ($prev, $this) = @_; + my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) = + parse_hunk_header($prev->{TEXT}[0]); + my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) = + parse_hunk_header($this->{TEXT}[0]); + + my (@line, $i, $ofs, $o_cnt, $n_cnt); + $ofs = $o0_ofs; + $o_cnt = $n_cnt = 0; + for ($i = 1; $i < @{$prev->{TEXT}}; $i++) { + my $line = $prev->{TEXT}[$i]; + if ($line =~ /^\+/) { + $n_cnt++; + push @line, $line; + next; + } + + last if ($o1_ofs <= $ofs); + + $o_cnt++; + $ofs++; + if ($line =~ /^ /) { + $n_cnt++; + } + push @line, $line; + } + + for ($i = 1; $i < @{$this->{TEXT}}; $i++) { + my $line = $this->{TEXT}[$i]; + if ($line =~ /^\+/) { + $n_cnt++; + push @line, $line; + next; + } + $ofs++; + $o_cnt++; + if ($line =~ /^ /) { + $n_cnt++; + } + push @line, $line; + } + my $head = ("@@ -$o0_ofs" . + (($o_cnt != 1) ? ",$o_cnt" : '') . + " +$n0_ofs" . + (($n_cnt != 1) ? ",$n_cnt" : '') . + " @@\n"); + @{$prev->{TEXT}} = ($head, @line); +} + +sub coalesce_overlapping_hunks { + my (@in) = @_; + my @out = (); + + my ($last_o_ctx, $last_was_dirty); + + for (grep { $_->{USE} } @in) { + my $text = $_->{TEXT}; + my ($o_ofs) = parse_hunk_header($text->[0]); + if (defined $last_o_ctx && + $o_ofs <= $last_o_ctx && + !$_->{DIRTY} && + !$last_was_dirty) { + merge_hunk($out[-1], $_); + } + else { + push @out, $_; + } + $last_o_ctx = find_last_o_ctx($out[-1]); + $last_was_dirty = $_->{DIRTY}; + } + return @out; +} sub color_diff { return map { @@ -804,6 +894,10 @@ EOF || $ENV{VISUAL} || $ENV{EDITOR} || "vi"; system('sh', '-c', $editor.' "$@"', $editor, $hunkfile); + if ($? != 0) { + return undef; + } + open $fh, '<', $hunkfile or die "failed to open hunk edit file for reading: " . $!; my @newtext = grep { !/^#/ } <$fh>; @@ -874,7 +968,8 @@ sub edit_hunk_loop { my $newhunk = { TEXT => $text, TYPE => $hunk->[$ix]->{TYPE}, - USE => 1 + USE => 1, + DIRTY => 1, }; if (diff_applies($head, @{$hunk}[0..$ix-1], @@ -1206,6 +1301,8 @@ sub patch_update_file { } } + @hunk = coalesce_overlapping_hunks(@hunk); + my $n_lofs = 0; my @result = (); for (@hunk) { @@ -36,12 +36,15 @@ cd_to_toplevel git var GIT_COMMITTER_IDENT >/dev/null || die "You need to set your committer info first" +if git rev-parse --verify -q HEAD >/dev/null +then + HAS_HEAD=yes +else + HAS_HEAD= +fi + sq () { - for sqarg - do - printf "%s" "$sqarg" | - sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/' - done + git rev-parse --sq-quote "$@" } stop_here () { @@ -290,16 +293,26 @@ else : >"$dotest/rebasing" else : >"$dotest/applying" - git update-ref ORIG_HEAD HEAD + if test -n "$HAS_HEAD" + then + git update-ref ORIG_HEAD HEAD + else + git update-ref -d ORIG_HEAD >/dev/null 2>&1 + fi fi fi case "$resolved" in '') - files=$(git diff-index --cached --name-only HEAD --) || exit + case "$HAS_HEAD" in + '') + files=$(git ls-files) ;; + ?*) + files=$(git diff-index --cached --name-only HEAD --) ;; + esac || exit if test "$files" then - : >"$dotest/dirtyindex" + test -n "$HAS_HEAD" && : >"$dotest/dirtyindex" die "Dirty index: cannot apply patches (dirty: $files)" fi esac @@ -541,18 +554,20 @@ do fi tree=$(git write-tree) && - parent=$(git rev-parse --verify HEAD) && commit=$( if test -n "$ignore_date" then GIT_AUTHOR_DATE= fi + parent=$(git rev-parse --verify -q HEAD) || + echo >&2 "applying to an empty history" + 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 commit-tree $tree ${parent:+-p} $parent <"$dotest/final-commit" ) && git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent || stop_here $this diff --git a/git-bisect.sh b/git-bisect.sh index 10ad340920..8969553658 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -33,16 +33,6 @@ require_work_tree _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" -sq() { - @@PERL@@ -e ' - for (@ARGV) { - s/'\''/'\'\\\\\'\''/g; - print " '\''$_'\''"; - } - print "\n"; - ' "$@" -} - bisect_autostart() { test -s "$GIT_DIR/BISECT_START" || { echo >&2 'You need to start by "git bisect start"' @@ -77,7 +67,7 @@ bisect_start() { then # Reset to the rev from where we started. start_head=$(cat "$GIT_DIR/BISECT_START") - git checkout "$start_head" || exit + git checkout "$start_head" -- || exit else # Get rev from where we start. case "$head" in @@ -107,7 +97,7 @@ bisect_start() { for arg; do case "$arg" in --) has_double_dash=1; break ;; esac done - orig_args=$(sq "$@") + orig_args=$(git rev-parse --sq-quote "$@") bad_seen=0 eval='' while [ $# -gt 0 ]; do @@ -147,7 +137,7 @@ bisect_start() { # Write new start state. # echo "$start_head" >"$GIT_DIR/BISECT_START" && - sq "$@" >"$GIT_DIR/BISECT_NAMES" && + git rev-parse --sq-quote "$@" >"$GIT_DIR/BISECT_NAMES" && eval "$eval" && echo "git bisect start$orig_args" >>"$GIT_DIR/BISECT_LOG" || exit # @@ -177,10 +167,6 @@ is_expected_rev() { test "$1" = $(cat "$GIT_DIR/BISECT_EXPECTED_REV") } -mark_expected_rev() { - echo "$1" > "$GIT_DIR/BISECT_EXPECTED_REV" -} - check_expected_revs() { for _rev in "$@"; do if ! is_expected_rev "$_rev"; then @@ -199,7 +185,7 @@ bisect_skip() { *..*) revs=$(git rev-list "$arg") || die "Bad rev input: $arg" ;; *) - revs=$(sq "$arg") ;; + revs=$(git rev-parse --sq-quote "$arg") ;; esac all="$all $revs" done @@ -279,240 +265,22 @@ bisect_auto_next() { bisect_next_check && bisect_next || : } -filter_skipped() { - _eval="$1" - _skip="$2" - - if [ -z "$_skip" ]; then - eval "$_eval" | { - while read line - do - echo "$line &&" - done - echo ':' - } - return - fi - - # Let's parse the output of: - # "git rev-list --bisect-vars --bisect-all ..." - eval "$_eval" | { - VARS= FOUND= TRIED= - while read hash line - do - case "$VARS,$FOUND,$TRIED,$hash" in - 1,*,*,*) - # "bisect_foo=bar" read from rev-list output. - echo "$hash &&" - ;; - ,*,*,---*) - # Separator - ;; - ,,,bisect_rev*) - # We had nothing to search. - echo "bisect_rev= &&" - VARS=1 - ;; - ,,*,bisect_rev*) - # We did not find a good bisect rev. - # This should happen only if the "bad" - # commit is also a "skip" commit. - echo "bisect_rev='$TRIED' &&" - VARS=1 - ;; - ,,*,*) - # We are searching. - TRIED="${TRIED:+$TRIED|}$hash" - case "$_skip" in - *$hash*) ;; - *) - echo "bisect_rev=$hash &&" - echo "bisect_tried='$TRIED' &&" - FOUND=1 - ;; - esac - ;; - ,1,*,bisect_rev*) - # We have already found a rev to be tested. - VARS=1 - ;; - ,1,*,*) - ;; - *) - # Unexpected input - echo "die 'filter_skipped error'" - die "filter_skipped error " \ - "VARS: '$VARS' " \ - "FOUND: '$FOUND' " \ - "TRIED: '$TRIED' " \ - "hash: '$hash' " \ - "line: '$line'" - ;; - esac - done - echo ':' - } -} - -exit_if_skipped_commits () { - _tried=$1 - if expr "$_tried" : ".*[|].*" > /dev/null ; then - echo "There are only 'skip'ped commit left to test." - echo "The first bad commit could be any of:" - echo "$_tried" | tr '[|]' '[\012]' - echo "We cannot bisect more!" - exit 2 - fi -} - -bisect_checkout() { - _rev="$1" - _msg="$2" - echo "Bisecting: $_msg" - mark_expected_rev "$_rev" - git checkout -q "$_rev" || exit - git show-branch "$_rev" -} - -is_among() { - _rev="$1" - _list="$2" - case "$_list" in *$_rev*) return 0 ;; esac - return 1 -} - -handle_bad_merge_base() { - _badmb="$1" - _good="$2" - if is_expected_rev "$_badmb"; then - cat >&2 <<EOF -The merge base $_badmb is bad. -This means the bug has been fixed between $_badmb and [$_good]. -EOF - exit 3 - else - cat >&2 <<EOF -Some good revs are not ancestor of the bad rev. -git bisect cannot work properly in this case. -Maybe you mistake good and bad revs? -EOF - exit 1 - fi -} - -handle_skipped_merge_base() { - _mb="$1" - _bad="$2" - _good="$3" - cat >&2 <<EOF -Warning: the merge base between $_bad and [$_good] must be skipped. -So we cannot be sure the first bad commit is between $_mb and $_bad. -We continue anyway. -EOF -} - -# -# "check_merge_bases" checks that merge bases are not "bad". -# -# - If one is "good", that's good, we have nothing to do. -# - If one is "bad", it means the user assumed something wrong -# and we must exit. -# - If one is "skipped", we can't know but we should warn. -# - If we don't know, we should check it out and ask the user to test. -# -# In the last case we will return 1, and otherwise 0. -# -check_merge_bases() { - _bad="$1" - _good="$2" - _skip="$3" - for _mb in $(git merge-base --all $_bad $_good) - do - if is_among "$_mb" "$_good"; then - continue - elif test "$_mb" = "$_bad"; then - handle_bad_merge_base "$_bad" "$_good" - elif is_among "$_mb" "$_skip"; then - handle_skipped_merge_base "$_mb" "$_bad" "$_good" - else - bisect_checkout "$_mb" "a merge base must be tested" - return 1 - fi - done - return 0 -} - -# -# "check_good_are_ancestors_of_bad" checks that all "good" revs are -# ancestor of the "bad" rev. -# -# If that's not the case, we need to check the merge bases. -# If a merge base must be tested by the user we return 1 and -# otherwise 0. -# -check_good_are_ancestors_of_bad() { - test -f "$GIT_DIR/BISECT_ANCESTORS_OK" && - return - - _bad="$1" - _good=$(echo $2 | sed -e 's/\^//g') - _skip="$3" - - # Bisecting with no good rev is ok - test -z "$_good" && return - - _side=$(git rev-list $_good ^$_bad) - if test -n "$_side"; then - # Return if a checkout was done - check_merge_bases "$_bad" "$_good" "$_skip" || return - fi - - : > "$GIT_DIR/BISECT_ANCESTORS_OK" - - return 0 -} - bisect_next() { case "$#" in 0) ;; *) usage ;; esac bisect_autostart bisect_next_check good - # Get bad, good and skipped revs - bad=$(git rev-parse --verify refs/bisect/bad) && - good=$(git for-each-ref --format='^%(objectname)' \ - "refs/bisect/good-*" | tr '\012' ' ') && - skip=$(git for-each-ref --format='%(objectname)' \ - "refs/bisect/skip-*" | tr '\012' ' ') || exit - - # Maybe some merge bases must be tested first - check_good_are_ancestors_of_bad "$bad" "$good" "$skip" - # Return now if a checkout has already been done - test "$?" -eq "1" && return - - # Get bisection information - BISECT_OPT='' - test -n "$skip" && BISECT_OPT='--bisect-all' - eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" && - eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" && - eval=$(filter_skipped "$eval" "$skip") && - eval "$eval" || exit - - if [ -z "$bisect_rev" ]; then - echo "$bad was both good and bad" - exit 1 - fi - if [ "$bisect_rev" = "$bad" ]; then - exit_if_skipped_commits "$bisect_tried" - echo "$bisect_rev is first bad commit" - git diff-tree --pretty $bisect_rev - exit 0 - fi + # Perform all bisection computation, display and checkout + git bisect--helper --next-all + res=$? + + # Check if we should exit because bisection is finished + test $res -eq 10 && exit 0 - # We should exit here only if the "bad" - # commit is also a "skip" commit (see above). - exit_if_skipped_commits "$bisect_rev" + # Check for an error in the bisection process + test $res -ne 0 && exit $res - bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this" + return 0 } bisect_visualize() { @@ -549,7 +317,7 @@ bisect_reset() { *) usage ;; esac - git checkout "$branch" && bisect_clean_state + git checkout "$branch" -- && bisect_clean_state } bisect_clean_state() { diff --git a/git-compat-util.h b/git-compat-util.h index 878d83dd08..919b7f1ade 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -7,7 +7,7 @@ /* * See if our compiler is known to support flexible array members. */ -#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && (!defined(__SUNPRO_C) || (__SUNPRO_C > 0x580)) # define FLEX_ARRAY /* empty */ #elif defined(__GNUC__) # if (__GNUC__ >= 3) @@ -39,13 +39,29 @@ /* Approximation of the length of the decimal representation of this type. */ #define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1) -#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && !defined(_M_UNIX) +#if defined(__sun__) + /* + * On Solaris, when _XOPEN_EXTENDED is set, its header file + * forces the programs to be XPG4v2, defeating any _XOPEN_SOURCE + * setting to say we are XPG5 or XPG6. Also on Solaris, + * XPG6 programs must be compiled with a c99 compiler, while + * non XPG6 programs must be compiled with a pre-c99 compiler. + */ +# if __STDC_VERSION__ - 0 >= 199901L +# define _XOPEN_SOURCE 600 +# else +# define _XOPEN_SOURCE 500 +# endif +#elif !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(__USLC__) && !defined(_M_UNIX) #define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */ +#ifndef __sun__ #define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */ #endif +#endif #define _ALL_SOURCE 1 #define _GNU_SOURCE 1 #define _BSD_SOURCE 1 +#define _NETBSD_SOURCE 1 #include <unistd.h> #include <stdio.h> @@ -96,6 +112,13 @@ #include "compat/mingw.h" #endif /* __MINGW32__ */ +#ifndef NO_LIBGEN_H +#include <libgen.h> +#else +#define basename gitbasename +extern char *gitbasename(char *); +#endif + #ifndef NO_ICONV #include <iconv.h> #endif @@ -166,7 +189,7 @@ static inline const char *skip_prefix(const char *str, const char *prefix) return strncmp(str, prefix, len) ? NULL : str + len; } -#ifdef NO_MMAP +#if defined(NO_MMAP) || defined(USE_WIN32_MMAP) #ifndef PROT_READ #define PROT_READ 1 @@ -180,13 +203,19 @@ static inline const char *skip_prefix(const char *str, const char *prefix) extern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset); extern int git_munmap(void *start, size_t length); +#else /* NO_MMAP || USE_WIN32_MMAP */ + +#include <sys/mman.h> + +#endif /* NO_MMAP || USE_WIN32_MMAP */ + +#ifdef NO_MMAP + /* This value must be multiple of (pagesize * 2) */ #define DEFAULT_PACKED_GIT_WINDOW_SIZE (1 * 1024 * 1024) #else /* NO_MMAP */ -#include <sys/mman.h> - /* This value must be multiple of (pagesize * 2) */ #define DEFAULT_PACKED_GIT_WINDOW_SIZE \ (sizeof(void*) >= 8 \ @@ -225,6 +254,11 @@ extern int gitsetenv(const char *, const char *, int); extern char *gitmkdtemp(char *); #endif +#ifdef NO_MKSTEMPS +#define mkstemps gitmkstemps +extern int gitmkstemps(char *, int); +#endif + #ifdef NO_UNSETENV #define unsetenv gitunsetenv extern void gitunsetenv(const char *); @@ -388,4 +422,30 @@ void git_qsort(void *base, size_t nmemb, size_t size, # define FORCE_DIR_SET_GID 0 #endif +#ifdef NO_NSEC +#undef USE_NSEC +#define ST_CTIME_NSEC(st) 0 +#define ST_MTIME_NSEC(st) 0 +#else +#ifdef USE_ST_TIMESPEC +#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctimespec.tv_nsec)) +#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtimespec.tv_nsec)) +#else +#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctim.tv_nsec)) +#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtim.tv_nsec)) +#endif +#endif + +#ifdef UNRELIABLE_FSTAT +#define fstat_is_reliable() 0 +#else +#define fstat_is_reliable() 1 +#endif + +/* + * Preserves errno, prints a message, but gives no warning for ENOENT. + * Always returns the return value of unlink(2). + */ +int unlink_or_warn(const char *path); + #endif diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl index 6d9f0ef0f9..a36df33926 100755 --- a/git-cvsexportcommit.perl +++ b/git-cvsexportcommit.perl @@ -8,9 +8,9 @@ use File::Basename qw(basename dirname); use File::Spec; use Git; -our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w, $opt_W); +our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w, $opt_W, $opt_k); -getopts('uhPpvcfam:d:w:W'); +getopts('uhPpvcfkam:d:w:W'); $opt_h && usage(); @@ -225,7 +225,14 @@ if (@canstatusfiles) { foreach my $name (keys %todo) { my $basename = basename($name); - $basename = "no file " . $basename if (exists($added{$basename})); + # CVS reports files that don't exist in the current revision as + # "no file $basename" in its "status" output, so we should + # anticipate that. Totally unknown files will have a status + # "Unknown". However, if they exist in the Attic, their status + # will be "Up-to-date" (this means they were added once but have + # been removed). + $basename = "no file $basename" if $added{$basename}; + $basename =~ s/^\s+//; $basename =~ s/\s+$//; @@ -233,31 +240,45 @@ if (@canstatusfiles) { $fullname{$basename} = $name; push (@canstatusfiles2, $name); delete($todo{$name}); - } + } } my @cvsoutput; @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2); foreach my $l (@cvsoutput) { - chomp $l; - if ($l =~ /^File:\s+(.*\S)\s+Status: (.*)$/) { - if (!exists($fullname{$1})) { - print STDERR "Huh? Status reported for unexpected file '$1'\n"; - } else { - $cvsstat{$fullname{$1}} = $2; - } - } + chomp $l; + next unless + my ($file, $status) = $l =~ /^File:\s+(.*\S)\s+Status: (.*)$/; + + my $fullname = $fullname{$file}; + print STDERR "Huh? Status '$status' reported for unexpected file '$file'\n" + unless defined $fullname; + + # This response means the file does not exist except in + # CVS's attic, so set the status accordingly + $status = "In-attic" + if $file =~ /^no file / + && $status eq 'Up-to-date'; + + $cvsstat{$fullname{$file}} = $status; } } } -# ... validate new files, +# ... Validate that new files have the correct status foreach my $f (@afiles) { - if (defined ($cvsstat{$f}) and $cvsstat{$f} ne "Unknown") { - $dirty = 1; + next unless defined(my $stat = $cvsstat{$f}); + + # This means the file has never been seen before + next if $stat eq 'Unknown'; + + # This means the file has been seen before but was removed + next if $stat eq 'In-attic'; + + $dirty = 1; warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n"; warn "Status was: $cvsstat{$f}\n"; - } } + # ... validate known files. foreach my $f (@files) { next if grep { $_ eq $f } @afiles; @@ -266,7 +287,26 @@ foreach my $f (@files) { $dirty = 1; warn "File $f not up to date but has status '$cvsstat{$f}' in your CVS checkout!\n"; } + + # Depending on how your GIT tree got imported from CVS you may + # have a conflict between expanded keywords in your CVS tree and + # unexpanded keywords in the patch about to be applied. + if ($opt_k) { + my $orig_file ="$f.orig"; + rename $f, $orig_file; + open(FILTER_IN, "<$orig_file") or die "Cannot open $orig_file\n"; + open(FILTER_OUT, ">$f") or die "Cannot open $f\n"; + while (<FILTER_IN>) + { + my $line = $_; + $line =~ s/\$([A-Z][a-z]+):[^\$]+\$/\$\1\$/g; + print FILTER_OUT $line; + } + close FILTER_IN; + close FILTER_OUT; + } } + if ($dirty) { if ($opt_f) { warn "The tree is not clean -- forced merge\n"; $dirty = 0; @@ -370,7 +410,7 @@ sleep(1); sub usage { print STDERR <<END; -Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-w cvsworkdir] [-m msgprefix] [ parent ] commit +Usage: GIT_DIR=/path/to/.git git cvsexportcommit [-h] [-p] [-v] [-c] [-f] [-u] [-k] [-w cvsworkdir] [-m msgprefix] [ parent ] commit END exit(1); } diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh new file mode 100755 index 0000000000..57e8e3256d --- /dev/null +++ b/git-difftool--helper.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# git-difftool--helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher. +# This script is typically launched by using the 'git difftool' +# convenience command. +# +# Copyright (c) 2009 David Aguilar + +# Load common functions from git-mergetool--lib +TOOL_MODE=diff +. git-mergetool--lib + +# difftool.prompt controls the default prompt/no-prompt behavior +# and is overridden with $GIT_DIFFTOOL*_PROMPT. +should_prompt () { + prompt=$(git config --bool difftool.prompt || echo true) + if test "$prompt" = true; then + test -z "$GIT_DIFFTOOL_NO_PROMPT" + else + test -n "$GIT_DIFFTOOL_PROMPT" + fi +} + +# Sets up shell variables and runs a 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" + + # $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 + run_merge_tool "$merge_tool" +} + +# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values +test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL" +test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL" + +if test -z "$merge_tool"; then + merge_tool="$(get_merge_tool)" || exit +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/git-difftool.perl b/git-difftool.perl new file mode 100755 index 0000000000..ba5e60a45e --- /dev/null +++ b/git-difftool.perl @@ -0,0 +1,92 @@ +#!/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. +# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL +# are exported 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>] [-y|--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; + if ($^O eq 'MSWin32' || $^O eq 'msys') { + return "$exe.exe"; + } + return $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' || $arg eq '--tool') { + usage() if $#ARGV <= $idx; + $ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1]; + $skip_next = 1; + next; + } + if ($arg =~ /^--tool=/) { + $ENV{GIT_DIFF_TOOL} = substr($arg, 7); + next; + } + if ($arg eq '-y' || $arg eq '--no-prompt') { + $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true'; + delete $ENV{GIT_DIFFTOOL_PROMPT}; + next; + } + if ($arg eq '--prompt') { + $ENV{GIT_DIFFTOOL_PROMPT} = 'true'; + delete $ENV{GIT_DIFFTOOL_NO_PROMPT}; + next; + } + if ($arg eq '-h' || $arg eq '--help') { + usage(); + } + push @command, $arg; + } + return @command +} + +setup_environment(); + +# ActiveState Perl for Win32 does not implement POSIX semantics of +# exec* system call. It just spawns the given executable and finishes +# the starting program, exiting with code 0. +# system will at least catch the errors returned by git diff, +# allowing the caller of git difftool better handling of failures. +my $rc = system(generate_command()); +exit($rc | ($rc >> 8)); diff --git a/git-filter-branch.sh b/git-filter-branch.sh index 9a09ba1382..37e044db40 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -232,7 +232,9 @@ while read sha1 type name do case "$force,$name" in ,$orig_namespace*) - die "Namespace $orig_namespace not empty" + die "Cannot create a new backup. +A previous backup already exists in $orig_namespace +Force overwriting the backup with -f" ;; t,$orig_namespace*) git update-ref -d "$name" $sha1 @@ -270,10 +272,10 @@ test $commits -eq 0 && die "Found nothing to rewrite" # Rewrite the commits -i=0 +git_filter_branch__commit_count=0 while read commit parents; do - i=$(($i+1)) - printf "\rRewrite $commit ($i/$commits)" + git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1)) + printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)" case "$filter_subdir" in "") @@ -428,7 +430,7 @@ if [ "$filter_tag_name" ]; then if [ "$type" = "tag" ]; then # Dereference to a commit sha1t="$sha1" - sha1="$(git rev-parse "$sha1"^{commit} 2>/dev/null)" || continue + sha1="$(git rev-parse -q "$sha1"^{commit})" || continue fi [ -f "../map/$sha1" ] || continue diff --git a/git-gui/Makefile b/git-gui/Makefile index 3ad8a21b30..b3580e9e48 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -105,8 +105,11 @@ endif ifeq ($(uname_S),Darwin) TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app - ifeq ($(shell expr "$(uname_R)" : '9\.'),2) - TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish\ Shell.app + ifeq ($(shell echo "$(uname_R)" | awk -F. '{if ($$1 >= 9) print "y"}')_$(shell test -d $(TKFRAMEWORK) || echo n),y_n) + TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish.app + ifeq ($(shell test -d $(TKFRAMEWORK) || echo n),n) + TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish\ Shell.app + endif endif TKEXECUTABLE = $(shell basename "$(TKFRAMEWORK)" .app) endif diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index e018e076f8..14b92ba786 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -122,6 +122,7 @@ unset oguimsg set _appname {Git Gui} set _gitdir {} set _gitexec {} +set _githtmldir {} set _reponame {} set _iscygwin {} set _search_path {} @@ -168,6 +169,28 @@ proc gitexec {args} { return [eval [list file join $_gitexec] $args] } +proc githtmldir {args} { + global _githtmldir + if {$_githtmldir eq {}} { + if {[catch {set _githtmldir [git --html-path]}]} { + # Git not installed or option not yet supported + return {} + } + if {[is_Cygwin]} { + set _githtmldir [exec cygpath \ + --windows \ + --absolute \ + $_githtmldir] + } else { + set _githtmldir [file normalize $_githtmldir] + } + } + if {$args eq {}} { + return $_githtmldir + } + return [eval [list file join $_githtmldir] $args] +} + proc reponame {} { return $::_reponame } @@ -640,10 +663,13 @@ font create font_diffbold font create font_diffitalic foreach class {Button Checkbutton Entry Label - Labelframe Listbox Menu Message + Labelframe Listbox Message Radiobutton Spinbox Text} { option add *$class.font font_ui } +if {![is_MacOSX]} { + option add *Menu.font font_ui +} unset class if {[is_Windows] || [is_MacOSX]} { @@ -699,7 +725,7 @@ proc apply_config {} { set default_config(branch.autosetupmerge) true set default_config(merge.tool) {} -set default_config(merge.keepbackup) true +set default_config(mergetool.keepbackup) true set default_config(merge.diffstat) true set default_config(merge.summary) false set default_config(merge.verbosity) 2 @@ -769,9 +795,9 @@ if {![regsub {^git version } $_git_version {} _git_version]} { set _real_git_version $_git_version regsub -- {[\-\.]dirty$} $_git_version {} _git_version regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version -regsub {\.rc[0-9]+$} $_git_version {} _git_version +regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version regsub {\.GIT$} $_git_version {} _git_version -regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version +regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} { catch {wm withdraw .} @@ -1108,6 +1134,7 @@ set current_diff_path {} set is_3way_diff 0 set is_conflict_diff 0 set selected_commit_type new +set diff_empty_count 0 set nullid "0000000000000000000000000000000000000000" set nullid2 "0000000000000000000000000000000000000001" @@ -1924,7 +1951,7 @@ proc do_explore {} { # freedesktop.org-conforming system is our best shot set explorer "xdg-open" } - eval exec $explorer [file dirname [gitdir]] & + eval exec $explorer [list [file nativename [file dirname [gitdir]]]] & } set is_quitting 0 @@ -2277,6 +2304,12 @@ set ui_comm {} # -- Menu Bar # menu .mbar -tearoff 0 +if {[is_MacOSX]} { + # -- Apple Menu (Mac OS X only) + # + .mbar add cascade -label Apple -menu .mbar.apple + menu .mbar.apple +} .mbar add cascade -label [mc Repository] -menu .mbar.repository .mbar add cascade -label [mc Edit] -menu .mbar.edit if {[is_enabled branch]} { @@ -2292,7 +2325,6 @@ if {[is_enabled transport]} { if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar add cascade -label [mc Tools] -menu .mbar.tools } -. configure -menu .mbar # -- Repository Menu # @@ -2545,19 +2577,7 @@ if {[is_enabled transport]} { } if {[is_MacOSX]} { - # -- Apple Menu (Mac OS X only) - # - .mbar add cascade -label Apple -menu .mbar.apple - menu .mbar.apple - - .mbar.apple add command -label [mc "About %s" [appname]] \ - -command do_about - .mbar.apple add separator - .mbar.apple add command \ - -label [mc "Preferences..."] \ - -command do_options \ - -accelerator $M1T-, - bind . <$M1B-,> do_options + proc ::tk::mac::ShowPreferences {} {do_options} } else { # -- Edit Menu # @@ -2585,17 +2605,23 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar add cascade -label [mc Help] -menu .mbar.help menu .mbar.help -if {![is_MacOSX]} { +if {[is_MacOSX]} { + .mbar.apple add command -label [mc "About %s" [appname]] \ + -command do_about + .mbar.apple add separator +} else { .mbar.help add command -label [mc "About %s" [appname]] \ -command do_about } +. configure -menu .mbar +set doc_path [githtmldir] +if {$doc_path ne {}} { + set doc_path [file join $doc_path index.html] -set doc_path [file dirname [gitexec]] -set doc_path [file join $doc_path Documentation index.html] - -if {[is_Cygwin]} { - set doc_path [exec cygpath --mixed $doc_path] + if {[is_Cygwin]} { + set doc_path [exec cygpath --mixed $doc_path] + } } if {[file isfile $doc_path]} { @@ -2944,7 +2970,7 @@ $ctxm add command \ -command {tk_textPaste $ui_comm} $ctxm add command \ -label [mc Delete] \ - -command {$ui_comm delete sel.first sel.last} + -command {catch {$ui_comm delete sel.first sel.last}} $ctxm add separator $ctxm add command \ -label [mc "Select All"] \ diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl index ef1930b491..20d5e42307 100644 --- a/git-gui/lib/branch_delete.tcl +++ b/git-gui/lib/branch_delete.tcl @@ -51,7 +51,7 @@ constructor dialog {} { $w.check \ [mc "Delete Only If Merged Into"] \ ] - $w_check none [mc "Always (Do not perform merge test.)"] + $w_check none [mc "Always (Do not perform merge checks)"] pack $w.check -anchor nw -fill x -pady 5 -padx 5 foreach h [load_all_heads] { @@ -112,7 +112,7 @@ method _delete {} { } if {$to_delete eq {}} return if {$check_cmt eq {}} { - set msg [mc "Recovering deleted branches is difficult. \n\n Delete the selected branches?"] + set msg [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"] if {[tk_messageBox \ -icon warning \ -type yesno \ diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl index caca88831b..9e7412c446 100644 --- a/git-gui/lib/checkout_op.tcl +++ b/git-gui/lib/checkout_op.tcl @@ -9,6 +9,7 @@ field w_cons {}; # embedded console window object field new_expr ; # expression the user saw/thinks this is field new_hash ; # commit SHA-1 we are switching to field new_ref ; # ref we are updating/creating +field old_hash ; # commit SHA-1 that was checked out when we started field parent_w .; # window that started us field merge_type none; # type of merge to apply to existing branch @@ -280,11 +281,11 @@ method _start_checkout {} { # -- Our in memory state should match the repository. # - repository_state curType curHEAD curMERGE_HEAD + repository_state curType old_hash curMERGE_HEAD if {[string match amend* $commit_type] && $curType eq {normal} - && $curHEAD eq $HEAD} { - } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} { + && $old_hash eq $HEAD} { + } elseif {$commit_type ne $curType || $HEAD ne $old_hash} { info_popup [mc "Last scanned state does not match repository state. Another Git program has modified this repository since the last scan. A rescan must be performed before the current branch can be changed. @@ -297,7 +298,7 @@ The rescan will be automatically started now. return } - if {$curHEAD eq $new_hash} { + if {$old_hash eq $new_hash} { _after_readtree $this } elseif {[is_config_true gui.trustmtime]} { _readtree $this @@ -453,13 +454,47 @@ method _after_readtree {} { If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."] } + # -- Run the post-checkout hook. + # + set fd_ph [githook_read post-checkout $old_hash $new_hash 1] + if {$fd_ph ne {}} { + global pch_error + set pch_error {} + fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} + fileevent $fd_ph readable [cb _postcheckout_wait $fd_ph] + } else { + _update_repo_state $this + } +} + +method _postcheckout_wait {fd_ph} { + global pch_error + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + hook_failed_popup post-checkout $pch_error 0 + } + unset pch_error + _update_repo_state $this + return + } + fconfigure $fd_ph -blocking 0 +} + +method _update_repo_state {} { # -- Update our repository state. If we were previously in # amend mode we need to toss the current buffer and do a # full rescan to update our file lists. If we weren't in # amend mode our file lists are accurate and we can avoid # the rescan. # + global selected_commit_type commit_type HEAD MERGE_HEAD PARENT + global ui_comm + unlock_index + set name [_name $this] set selected_commit_type new if {[string match amend* $commit_type]} { $ui_comm delete 0.0 end diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl index f9ff62a3b2..633cc572bb 100644 --- a/git-gui/lib/choose_repository.tcl +++ b/git-gui/lib/choose_repository.tcl @@ -398,6 +398,8 @@ method _do_new {} { grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew pack $w_body.where -fill x + grid columnconfigure $w_body.where 1 -weight 1 + trace add variable @local_path write [cb _write_local_path] bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]] update @@ -964,7 +966,34 @@ method _readtree_wait {fd} { return } - set done 1 + # -- Run the post-checkout hook. + # + set fd_ph [githook_read post-checkout [string repeat 0 40] \ + [git rev-parse HEAD] 1] + if {$fd_ph ne {}} { + global pch_error + set pch_error {} + fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} + fileevent $fd_ph readable [cb _postcheckout_wait $fd_ph] + } else { + set done 1 + } +} + +method _postcheckout_wait {fd_ph} { + global pch_error + + append pch_error [read $fd_ph] + fconfigure $fd_ph -blocking 1 + if {[eof $fd_ph]} { + if {[catch {close $fd_ph}]} { + hook_failed_popup post-checkout $pch_error 0 + } + unset pch_error + set done 1 + return + } + fconfigure $fd_ph -blocking 0 } ###################################################################### @@ -998,6 +1027,8 @@ method _do_open {} { grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew pack $w_body.where -fill x + grid columnconfigure $w_body.where 1 -weight 1 + trace add variable @local_path write [cb _write_local_path] bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]] update diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl index 9cc8410595..7f459cd564 100644 --- a/git-gui/lib/commit.tcl +++ b/git-gui/lib/commit.tcl @@ -115,6 +115,23 @@ proc create_new_commit {} { rescan ui_ready } +proc setup_commit_encoding {msg_wt {quiet 0}} { + global repo_config + + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + set use_enc [tcl_encoding $enc] + if {$use_enc ne {}} { + fconfigure $msg_wt -encoding $use_enc + } else { + if {!$quiet} { + error_popup [mc "warning: Tcl does not support encoding '%s'." $enc] + } + fconfigure $msg_wt -encoding utf-8 + } +} + proc commit_tree {} { global HEAD commit_type file_states ui_comm repo_config global pch_error @@ -200,16 +217,7 @@ A good commit message has the following format: set msg_p [gitdir GITGUI_EDITMSG] set msg_wt [open $msg_p w] fconfigure $msg_wt -translation lf - if {[catch {set enc $repo_config(i18n.commitencoding)}]} { - set enc utf-8 - } - set use_enc [tcl_encoding $enc] - if {$use_enc ne {}} { - fconfigure $msg_wt -encoding $use_enc - } else { - error_popup [mc "warning: Tcl does not support encoding '%s'." $enc] - fconfigure $msg_wt -encoding utf-8 - } + setup_commit_encoding $msg_wt puts $msg_wt $msg close $msg_wt @@ -362,6 +370,7 @@ A rescan will be automatically started now. append reflogm " ($commit_type)" } set msg_fd [open $msg_p r] + setup_commit_encoding $msg_fd 1 gets $msg_fd subject close $msg_fd append reflogm {: } $subject @@ -398,8 +407,8 @@ A rescan will be automatically started now. # set fd_ph [githook_read post-commit] if {$fd_ph ne {}} { - upvar #0 pch_error$cmt_id pc_err - set pc_err {} + global pch_error + set pch_error {} fconfigure $fd_ph -blocking 0 -translation binary -eofchar {} fileevent $fd_ph readable \ [list commit_postcommit_wait $fd_ph $cmt_id] @@ -461,7 +470,7 @@ A rescan will be automatically started now. } proc commit_postcommit_wait {fd_ph cmt_id} { - upvar #0 pch_error$cmt_id pch_error + global pch_error append pch_error [read $fd_ph] fconfigure $fd_ph -blocking 1 diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index bbbf15c875..925b3f56c1 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -51,11 +51,16 @@ proc force_diff_encoding {enc} { proc handle_empty_diff {} { global current_diff_path file_states file_lists + global diff_empty_count set path $current_diff_path set s $file_states($path) if {[lindex $s 0] ne {_M}} return + # Prevent infinite rescan loops + incr diff_empty_count + if {$diff_empty_count > 1} return + info_popup [mc "No differences detected. %s has no changes. @@ -310,6 +315,7 @@ proc read_diff {fd cont_info} { global ui_diff diff_active global is_3way_diff is_conflict_diff current_diff_header global current_diff_queue + global diff_empty_count $ui_diff conf -state normal while {[gets $fd line] >= 0} { @@ -415,7 +421,10 @@ proc read_diff {fd cont_info} { if {[$ui_diff index end] eq {2.0}} { handle_empty_diff + } else { + set diff_empty_count 0 } + set callback [lindex $cont_info 1] if {$callback ne {}} { eval $callback diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl index eb2b4b56a4..3fe90e6970 100644 --- a/git-gui/lib/mergetool.tcl +++ b/git-gui/lib/mergetool.tcl @@ -88,7 +88,7 @@ proc merge_load_stages {path cont} { set merge_stages(3) {} set merge_stages_buf {} - set merge_stages_fd [eval git_read ls-files -u -z -- $path] + set merge_stages_fd [eval git_read ls-files -u -z -- {$path}] fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont] @@ -382,7 +382,7 @@ proc merge_tool_finish {fd} { delete_temp_files $mtool_tmpfiles ui_status [mc "Merge tool failed."] } else { - if {[is_config_true merge.keepbackup]} { + if {[is_config_true mergetool.keepbackup]} { file rename -force -- $backup "$mtool_target.orig" } diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl index 89eb0f70f2..4e02fc0d39 100644 --- a/git-gui/lib/remote_branch_delete.tcl +++ b/git-gui/lib/remote_branch_delete.tcl @@ -213,9 +213,7 @@ method _delete {} { -type yesno \ -title [wm title $w] \ -parent $w \ - -message [mc "Recovering deleted branches is difficult. - -Delete the selected branches?"]] ne yes} { + -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} { return } diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl index 38c3151b05..2f20eb39c0 100644 --- a/git-gui/lib/shortcut.tcl +++ b/git-gui/lib/shortcut.tcl @@ -54,7 +54,7 @@ proc do_cygwin_shortcut {} { $argv0] win32_create_lnk $fn [list \ $sh -c \ - "CHERE_INVOKING=1 source /etc/profile;[sq $me]" \ + "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \ ] \ [file dirname [file normalize [gitdir]]] } err]} { diff --git a/git-gui/lib/tools.tcl b/git-gui/lib/tools.tcl index 6ae63b6c7c..95e6e5553e 100644 --- a/git-gui/lib/tools.tcl +++ b/git-gui/lib/tools.tcl @@ -146,7 +146,7 @@ proc tools_complete {fullname w {ok 1}} { } if {$ok} { - set msg [mc "Tool completed succesfully: %s" $fullname] + set msg [mc "Tool completed successfully: %s" $fullname] } else { set msg [mc "Tool failed: %s" $fullname] } diff --git a/git-gui/po/de.po b/git-gui/po/de.po index a6f730b4eb..51abb50bb6 100644 --- a/git-gui/po/de.po +++ b/git-gui/po/de.po @@ -773,16 +773,6 @@ msgstr "Immer (ohne Zusammenführungstest)" msgid "The following branches are not completely merged into %s:" msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"Gelöschte Zweige können nur mit größerem Aufwand wiederhergestellt werden.\n" -"\n" -"Gewählte Zweige jetzt löschen?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2506,7 +2496,7 @@ msgstr "Starten: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "Werkzeug erfolgreich abgeschlossen: %s" #: lib/tools.tcl:151 diff --git a/git-gui/po/fr.po b/git-gui/po/fr.po index 45773ab3d8..a944ace6ce 100644 --- a/git-gui/po/fr.po +++ b/git-gui/po/fr.po @@ -62,7 +62,7 @@ msgstr "" "\n" "%s nécessite au moins Git 1.5.0.\n" "\n" -"Peut'on considérer que '%s' est en version 1.5.0 ?\n" +"Peut-on considérer que '%s' est en version 1.5.0 ?\n" #: git-gui.sh:1062 msgid "Git directory not found:" @@ -82,7 +82,7 @@ msgstr "Aucun répertoire de travail" #: git-gui.sh:1247 lib/checkout_op.tcl:305 msgid "Refreshing file status..." -msgstr "Rafraichissement du status des fichiers..." +msgstr "Rafraîchissement du statut des fichiers..." #: git-gui.sh:1303 msgid "Scanning for modified files ..." @@ -163,7 +163,7 @@ msgstr "Dépôt" #: git-gui.sh:2281 msgid "Edit" -msgstr "Edition" +msgstr "Édition" #: git-gui.sh:2283 lib/choose_rev.tcl:561 msgid "Branch" @@ -199,7 +199,7 @@ msgstr "Naviguer dans la branche..." #: git-gui.sh:2316 msgid "Visualize Current Branch's History" -msgstr "Visualiser historique branche courante" +msgstr "Visualiser l'historique de la branche courante" #: git-gui.sh:2320 msgid "Visualize All Branch History" @@ -208,12 +208,12 @@ msgstr "Voir l'historique de toutes les branches" #: git-gui.sh:2327 #, tcl-format msgid "Browse %s's Files" -msgstr "Naviguer l'arborescence de %s" +msgstr "Parcourir l'arborescence de %s" #: git-gui.sh:2329 #, tcl-format msgid "Visualize %s's History" -msgstr "Voir l'historique de la branche: %s" +msgstr "Voir l'historique de la branche : %s" #: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" @@ -230,7 +230,7 @@ msgstr "Vérifier le dépôt" #: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 #: lib/shortcut.tcl:39 lib/shortcut.tcl:71 msgid "Create Desktop Icon" -msgstr "Créer icône sur bureau" +msgstr "Créer une icône sur le bureau" #: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" @@ -320,7 +320,7 @@ msgstr "Désindexer" #: git-gui.sh:2484 lib/index.tcl:410 msgid "Revert Changes" -msgstr "Annuler les modifications (revert)" +msgstr "Annuler les modifications" #: git-gui.sh:2491 git-gui.sh:3069 msgid "Show Less Context" @@ -382,7 +382,7 @@ msgstr "Documentation en ligne" #: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 msgid "Show SSH Key" -msgstr "Montrer clé SSH" +msgstr "Montrer la clé SSH" #: git-gui.sh:2707 #, tcl-format @@ -445,7 +445,7 @@ msgstr "Fichier :" #: git-gui.sh:3078 msgid "Refresh" -msgstr "Rafraichir" +msgstr "Rafraîchir" #: git-gui.sh:3099 msgid "Decrease Font Size" @@ -457,7 +457,7 @@ msgstr "Agrandir la police" #: git-gui.sh:3111 lib/blame.tcl:281 msgid "Encoding" -msgstr "Encodage" +msgstr "Codage des caractères" #: git-gui.sh:3122 msgid "Apply/Reverse Hunk" @@ -469,7 +469,7 @@ msgstr "Appliquer/Inverser la ligne" #: git-gui.sh:3137 msgid "Run Merge Tool" -msgstr "Lancer outil de merge" +msgstr "Lancer l'outil de fusion" #: git-gui.sh:3142 msgid "Use Remote Version" @@ -527,7 +527,7 @@ msgid "" "Tcl binary distributed by Cygwin." msgstr "" "\n" -"Ceci est du à un problème connu avec\n" +"Ceci est dû à un problème connu avec\n" "le binaire Tcl distribué par Cygwin." #: git-gui.sh:3336 @@ -630,11 +630,11 @@ msgstr "Fichier original :" #: lib/blame.tcl:1021 msgid "Cannot find HEAD commit:" -msgstr "Impossible de trouver le commit HEAD:" +msgstr "Impossible de trouver le commit HEAD :" #: lib/blame.tcl:1076 msgid "Cannot find parent commit:" -msgstr "Impossible de trouver le commit parent:" +msgstr "Impossible de trouver le commit parent :" #: lib/blame.tcl:1091 msgid "Unable to display parent" @@ -646,7 +646,7 @@ msgstr "Erreur lors du chargement des différences :" #: lib/blame.tcl:1232 msgid "Originally By:" -msgstr "A l'origine par :" +msgstr "À l'origine par :" #: lib/blame.tcl:1238 msgid "In File:" @@ -691,11 +691,11 @@ msgstr "Détacher de la branche locale" #: lib/branch_create.tcl:22 msgid "Create Branch" -msgstr "Créer branche" +msgstr "Créer une branche" #: lib/branch_create.tcl:27 msgid "Create New Branch" -msgstr "Créer nouvelle branche" +msgstr "Créer une nouvelle branche" #: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 msgid "Create" @@ -719,7 +719,7 @@ msgstr "Révision initiale" #: lib/branch_create.tcl:72 msgid "Update Existing Branch:" -msgstr "Mettre à jour branche existante :" +msgstr "Mettre à jour une branche existante :" #: lib/branch_create.tcl:75 msgid "No" @@ -727,7 +727,7 @@ msgstr "Non" #: lib/branch_create.tcl:80 msgid "Fast Forward Only" -msgstr "Mise-à -jour rectiligne seulement (fast-forward)" +msgstr "Mise à jour rectiligne seulement (fast-forward)" #: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 msgid "Reset" @@ -769,7 +769,7 @@ msgstr "Branches locales" #: lib/branch_delete.tcl:52 msgid "Delete Only If Merged Into" -msgstr "Supprimer seulement si fusionnée dans:" +msgstr "Supprimer seulement si fusionnée dans :" #: lib/branch_delete.tcl:54 msgid "Always (Do not perform merge test.)" @@ -780,23 +780,13 @@ msgstr "Toujours (Ne pas faire de test de fusion.)" msgid "The following branches are not completely merged into %s:" msgstr "Les branches suivantes ne sont pas complètement fusionnées dans %s :" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"Récupérer des branches supprimées est difficile.\n" -"\n" -"Supprimer les branches sélectionnées ?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" "Failed to delete branches:\n" "%s" msgstr "" -"La suppression des branches suivantes a échouée :\n" +"La suppression des branches suivantes a échoué :\n" "%s" #: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22 @@ -902,11 +892,11 @@ msgstr "La stratégie de fusion '%s' n'est pas supportée." #: lib/checkout_op.tcl:261 #, tcl-format msgid "Failed to update '%s'." -msgstr "La mise à jour de '%s' a échouée." +msgstr "La mise à jour de '%s' a échoué." #: lib/checkout_op.tcl:273 msgid "Staging area (index) is already locked." -msgstr "L'index (staging area) est déjà vérouillé" +msgstr "L'index (staging area) est déjà verrouillé." #: lib/checkout_op.tcl:288 msgid "" @@ -918,7 +908,7 @@ msgid "" "The rescan will be automatically started now.\n" msgstr "" "L'état lors de la dernière synchronisation ne correspond plus à l'état du " -"dépôt\n" +"dépôt.\n" "\n" "Un autre programme Git a modifié ce dépôt depuis la dernière " "synchronisation. Une resynchronisation doit être effectuée avant de pouvoir " @@ -956,9 +946,9 @@ msgid "" "If you wanted to be on a branch, create one now starting from 'This Detached " "Checkout'." msgstr "" -"Vous n'êtes plus ur une branche locale.\n" +"Vous n'êtes plus sur une branche locale.\n" "\n" -"Si vous vouliez être sur une branche, créez en une maintenant en partant de " +"Si vous vouliez être sur une branche, créez-en une maintenant en partant de " "'Cet emprunt détaché'." #: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 @@ -1000,7 +990,7 @@ msgstr "" "mis à jour avec succès, mais la mise à jour d'un fichier interne à Git a " "échouée.\n" "\n" -"Cela n'aurait pas du se produire. %s va abandonner et se terminer." +"Cela n'aurait pas dû se produire. %s va abandonner et se terminer." #: lib/choose_font.tcl:39 msgid "Select" @@ -1023,8 +1013,8 @@ msgid "" "This is example text.\n" "If you like this text, it can be your font." msgstr "" -"C'est un texte d'exemple.\n" -"Si vous aimez ce texte, vous pouvez choisir cette police" +"Ceci est un texte d'exemple.\n" +"Si vous aimez ce texte, vous pouvez choisir cette police." #: lib/choose_repository.tcl:28 msgid "Git Gui" @@ -1040,7 +1030,7 @@ msgstr "Nouveau..." #: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 msgid "Clone Existing Repository" -msgstr "Cloner dépôt existant" +msgstr "Cloner un dépôt existant" #: lib/choose_repository.tcl:106 msgid "Clone..." @@ -1048,7 +1038,7 @@ msgstr "Cloner..." #: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 msgid "Open Existing Repository" -msgstr "Ouvrir dépôt existant" +msgstr "Ouvrir un dépôt existant" #: lib/choose_repository.tcl:119 msgid "Open..." @@ -1056,17 +1046,17 @@ msgstr "Ouvrir..." #: lib/choose_repository.tcl:132 msgid "Recent Repositories" -msgstr "Dépôt récemment utilisés" +msgstr "Dépôts récemment utilisés" #: lib/choose_repository.tcl:138 msgid "Open Recent Repository:" -msgstr "Ouvrir dépôt récent :" +msgstr "Ouvrir un dépôt récent :" #: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 #: lib/choose_repository.tcl:316 #, tcl-format msgid "Failed to create repository %s:" -msgstr "La création du dépôt %s a échouée :" +msgstr "La création du dépôt %s a échoué :" #: lib/choose_repository.tcl:387 msgid "Directory:" @@ -1093,11 +1083,11 @@ msgstr "Cloner" #: lib/choose_repository.tcl:473 msgid "Source Location:" -msgstr "Emplacement source:" +msgstr "Emplacement source :" #: lib/choose_repository.tcl:484 msgid "Target Directory:" -msgstr "Répertoire cible:" +msgstr "Répertoire cible :" #: lib/choose_repository.tcl:496 msgid "Clone Type:" @@ -1137,7 +1127,7 @@ msgstr "L'emplacement %s existe déjà ." #: lib/choose_repository.tcl:622 msgid "Failed to configure origin" -msgstr "La configuration de l'origine a échouée." +msgstr "La configuration de l'origine a échoué." #: lib/choose_repository.tcl:634 msgid "Counting objects" @@ -1242,7 +1232,7 @@ msgstr "fichiers" #: lib/choose_repository.tcl:962 msgid "Initial file checkout failed." -msgstr "Chargement initial du fichier échoué." +msgstr "Le chargement initial du fichier a échoué." #: lib/choose_repository.tcl:978 msgid "Open" @@ -1284,7 +1274,7 @@ msgstr "Révision invalide : %s" #: lib/choose_rev.tcl:338 msgid "No revision selected." -msgstr "Pas de révision selectionnée." +msgstr "Pas de révision sélectionnée." #: lib/choose_rev.tcl:346 msgid "Revision expression is empty." @@ -1292,7 +1282,7 @@ msgstr "L'expression de révision est vide." #: lib/choose_rev.tcl:531 msgid "Updated" -msgstr "Mise-à -jour:" +msgstr "Mise à jour:" #: lib/choose_rev.tcl:559 msgid "URL" @@ -1320,8 +1310,8 @@ msgid "" msgstr "" "Impossible de corriger pendant une fusion.\n" "\n" -"Vous êtes actuellement au milieu d'une fusion qui n'a pas été completement " -"terminée. Vous ne pouvez pas corriger le commit précédant sauf si vous " +"Vous êtes actuellement au milieu d'une fusion qui n'a pas été complètement " +"terminée. Vous ne pouvez pas corriger le commit précédent sauf si vous " "abandonnez la fusion courante.\n" #: lib/commit.tcl:49 @@ -1409,7 +1399,7 @@ msgstr "" #: lib/commit.tcl:211 #, tcl-format msgid "warning: Tcl does not support encoding '%s'." -msgstr "attention : Tcl ne supporte pas l'encodage '%s'." +msgstr "attention : Tcl ne supporte pas le codage '%s'." #: lib/commit.tcl:227 msgid "Calling pre-commit hook..." @@ -1469,12 +1459,12 @@ msgstr "commit-tree a échoué :" #: lib/commit.tcl:373 msgid "update-ref failed:" -msgstr "update-ref a échoué" +msgstr "update-ref a échoué :" #: lib/commit.tcl:461 #, tcl-format msgid "Created commit %s: %s" -msgstr "Commit créé %s : %s" +msgstr "Commit %s créé : %s" #: lib/console.tcl:59 msgid "Working... please wait..." @@ -1581,24 +1571,24 @@ msgid "" "LOCAL: deleted\n" "REMOTE:\n" msgstr "" -"LOCAL: supprimé\n" -"DISTANT:\n" +"LOCAL : supprimé\n" +"DISTANT :\n" #: lib/diff.tcl:125 msgid "" "REMOTE: deleted\n" "LOCAL:\n" msgstr "" -"DISTANT: supprimé\n" -"LOCAL:\n" +"DISTANT : supprimé\n" +"LOCAL :\n" #: lib/diff.tcl:132 msgid "LOCAL:\n" -msgstr "LOCAL:\n" +msgstr "LOCAL :\n" #: lib/diff.tcl:135 msgid "REMOTE:\n" -msgstr "DISTANT:\n" +msgstr "DISTANT :\n" #: lib/diff.tcl:197 lib/diff.tcl:296 #, tcl-format @@ -1624,7 +1614,7 @@ msgid "" "* Showing only first %d bytes.\n" msgstr "" "* Le fichier non suivi fait %d octets.\n" -"* On montre seulement les premiers %d octets.\n" +"* Seuls les %d premiers octets sont montrés.\n" #: lib/diff.tcl:228 #, tcl-format @@ -1635,7 +1625,7 @@ msgid "" msgstr "" "\n" "* Fichier suivi raccourcis ici de %s.\n" -"* Pour voir le fichier entier, utiliser un éditeur externe.\n" +"* Pour voir le fichier entier, utilisez un éditeur externe.\n" #: lib/diff.tcl:436 msgid "Failed to unstage selected hunk." @@ -1680,7 +1670,7 @@ msgstr "Vous devez corriger les erreurs suivantes avant de pouvoir commiter." #: lib/index.tcl:6 msgid "Unable to unlock the index." -msgstr "Impossible de dévérouiller l'index." +msgstr "Impossible de déverrouiller l'index." #: lib/index.tcl:15 msgid "Index Error" @@ -1700,12 +1690,12 @@ msgstr "Continuer" #: lib/index.tcl:31 msgid "Unlock Index" -msgstr "Déverouiller l'index" +msgstr "Déverrouiller l'index" #: lib/index.tcl:287 #, tcl-format msgid "Unstaging %s from commit" -msgstr "Désindexation de: %s" +msgstr "Désindexation de : %s" #: lib/index.tcl:326 msgid "Ready to commit." @@ -1804,11 +1794,11 @@ msgid "" msgstr "" "Vous êtes au milieu d'une modification.\n" "\n" -"Le fichier %s est modifié.\n" +"Le fichier %s a été modifié.\n" "\n" "Vous devriez terminer le commit courant avant de lancer une fusion. En " "faisait comme cela, vous éviterez de devoir éventuellement abandonner une " -"fusion ayant échouée.\n" +"fusion ayant échoué.\n" #: lib/merge.tcl:107 #, tcl-format @@ -1826,7 +1816,7 @@ msgstr "La fusion s'est faite avec succès." #: lib/merge.tcl:133 msgid "Merge failed. Conflict resolution is required." -msgstr "La fusion a echouée. Il est nécessaire de résoudre les conflicts." +msgstr "La fusion a echoué. Il est nécessaire de résoudre les conflits." #: lib/merge.tcl:158 #, tcl-format @@ -1914,16 +1904,16 @@ msgid "" "\n" "This operation can be undone only by restarting the merge." msgstr "" -"Noter que le diff ne montre que les modifications en conflict.\n" +"Noter que le diff ne montre que les modifications en conflit.\n" "\n" "%s sera écrasé.\n" "\n" -"Cette opération ne peut être défaite qu'en relançant la fusion." +"Cette opération ne peut être inversée qu'en relançant la fusion." #: lib/mergetool.tcl:45 #, tcl-format msgid "File %s seems to have unresolved conflicts, still stage?" -msgstr "Le fichier %s semble avoir des conflicts non résolus, indéxer quand même ?" +msgstr "Le fichier %s semble avoir des conflits non résolus, indexer quand même ?" #: lib/mergetool.tcl:60 #, tcl-format @@ -1932,11 +1922,11 @@ msgstr "Ajouter une résolution pour %s" #: lib/mergetool.tcl:141 msgid "Cannot resolve deletion or link conflicts using a tool" -msgstr "Impossible de résoudre la suppression ou de relier des conflicts en utilisant un outil" +msgstr "Impossible de résoudre la suppression ou de relier des conflits en utilisant un outil" #: lib/mergetool.tcl:146 msgid "Conflict file does not exist" -msgstr "Le fichier en conflict n'existe pas." +msgstr "Le fichier en conflit n'existe pas." #: lib/mergetool.tcl:264 #, tcl-format @@ -1958,7 +1948,7 @@ msgid "" "Error retrieving versions:\n" "%s" msgstr "" -"Erreur lors de la récupération des versions:\n" +"Erreur lors de la récupération des versions :\n" "%s" #: lib/mergetool.tcl:343 @@ -1968,7 +1958,7 @@ msgid "" "\n" "%s" msgstr "" -"Impossible de lancer l'outil de fusion:\n" +"Impossible de lancer l'outil de fusion :\n" "\n" "%s" @@ -1983,12 +1973,12 @@ msgstr "L'outil de fusion a échoué." #: lib/option.tcl:11 #, tcl-format msgid "Invalid global encoding '%s'" -msgstr "Encodage global invalide '%s'" +msgstr "Codage global '%s' invalide" #: lib/option.tcl:19 #, tcl-format msgid "Invalid repo encoding '%s'" -msgstr "Encodage de dépôt invalide '%s'" +msgstr "Codage de dépôt '%s' invalide" #: lib/option.tcl:117 msgid "Restore Defaults" @@ -2001,7 +1991,7 @@ msgstr "Sauvegarder" #: lib/option.tcl:131 #, tcl-format msgid "%s Repository" -msgstr "Dépôt: %s" +msgstr "Dépôt : %s" #: lib/option.tcl:132 msgid "Global (All Repositories)" @@ -2069,7 +2059,7 @@ msgstr "Nouveau modèle de nom de branche" #: lib/option.tcl:155 msgid "Default File Contents Encoding" -msgstr "Encodage du contenu des fichiers par défaut" +msgstr "Codage du contenu des fichiers par défaut" #: lib/option.tcl:203 msgid "Change" @@ -2098,11 +2088,11 @@ msgstr "Préférences" #: lib/option.tcl:314 msgid "Failed to completely save options:" -msgstr "La sauvegarde complète des options a échouée :" +msgstr "La sauvegarde complète des options a échoué :" #: lib/remote.tcl:163 msgid "Remove Remote" -msgstr "Supprimer dépôt distant" +msgstr "Supprimer un dépôt distant" #: lib/remote.tcl:168 msgid "Prune from" @@ -2118,11 +2108,11 @@ msgstr "Pousser vers" #: lib/remote_add.tcl:19 msgid "Add Remote" -msgstr "Ajouter dépôt distant" +msgstr "Ajouter un dépôt distant" #: lib/remote_add.tcl:24 msgid "Add New Remote" -msgstr "Ajouter nouveau dépôt distant" +msgstr "Ajouter un nouveau dépôt distant" #: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36 msgid "Add" @@ -2134,7 +2124,7 @@ msgstr "Détails des dépôts distants" #: lib/remote_add.tcl:50 msgid "Location:" -msgstr "Emplacement:" +msgstr "Emplacement :" #: lib/remote_add.tcl:62 msgid "Further Action" @@ -2146,7 +2136,7 @@ msgstr "Récupérer immédiatement" #: lib/remote_add.tcl:71 msgid "Initialize Remote Repository and Push" -msgstr "Initialiser dépôt distant et pousser" +msgstr "Initialiser un dépôt distant et pousser" #: lib/remote_add.tcl:77 msgid "Do Nothing Else Now" @@ -2193,7 +2183,7 @@ msgstr "Mise en place de %s (à %s)" #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 msgid "Delete Branch Remotely" -msgstr "Supprimer branche à distance" +msgstr "Supprimer une branche à distance" #: lib/remote_branch_delete.tcl:47 msgid "From Repository" @@ -2244,8 +2234,8 @@ msgid "" "One or more of the merge tests failed because you have not fetched the " "necessary commits. Try fetching from %s first." msgstr "" -"Une ou plusieurs des tests de fusion ont échoués parce que vous n'avez pas " -"récupéré les commits nécessaires. Essayez de récupéré à partir de %s d'abord." +"Un ou plusieurs des tests de fusion ont échoué parce que vous n'avez pas " +"récupéré les commits nécessaires. Essayez de récupérer à partir de %s d'abord." #: lib/remote_branch_delete.tcl:207 msgid "Please select one or more branches to delete." @@ -2257,14 +2247,14 @@ msgid "" "\n" "Delete the selected branches?" msgstr "" -"Récupérer des branches supprimées est difficile.\n" +"Il est difficile de récupérer des branches supprimées.\n" "\n" -"Souhaitez vous supprimer les branches sélectionnées ?" +"Supprimer les branches sélectionnées ?" #: lib/remote_branch_delete.tcl:226 #, tcl-format msgid "Deleting branches from %s" -msgstr "Supprimer les branches de %s" +msgstr "Suppression des branches de %s" #: lib/remote_branch_delete.tcl:286 msgid "No repository selected." @@ -2285,7 +2275,7 @@ msgstr "Suivant" #: lib/search.tcl:24 msgid "Prev" -msgstr "Précédant" +msgstr "Précédent" #: lib/search.tcl:25 msgid "Case-Sensitive" @@ -2293,7 +2283,7 @@ msgstr "Sensible à la casse" #: lib/shortcut.tcl:20 lib/shortcut.tcl:61 msgid "Cannot write shortcut:" -msgstr "Impossible d'écrire le raccourcis :" +msgstr "Impossible d'écrire le raccourci :" #: lib/shortcut.tcl:136 msgid "Cannot write icon:" @@ -2318,7 +2308,7 @@ msgstr "Réinitialisation du dictionnaire à %s." #: lib/spellcheck.tcl:73 msgid "Spell checker silently failed on startup" -msgstr "La vérification d'orthographe a échouée silentieusement au démarrage" +msgstr "La vérification d'orthographe a échoué silencieusement au démarrage" #: lib/spellcheck.tcl:80 msgid "Unrecognized spell checker" @@ -2351,11 +2341,11 @@ msgstr "Générer une clé" #: lib/sshkey.tcl:56 msgid "Copy To Clipboard" -msgstr "Copier dans le presse papier" +msgstr "Copier dans le presse-papier" #: lib/sshkey.tcl:70 msgid "Your OpenSSH Public Key" -msgstr "Votre clé publique Open SSH" +msgstr "Votre clé publique OpenSSH" #: lib/sshkey.tcl:78 msgid "Generating..." @@ -2368,7 +2358,7 @@ msgid "" "\n" "%s" msgstr "" -"Impossible de lancer ssh-keygen:\n" +"Impossible de lancer ssh-keygen :\n" "\n" "%s" @@ -2398,7 +2388,7 @@ msgstr "Lancer %s nécessite qu'un fichier soit sélectionné." #: lib/tools.tcl:90 #, tcl-format msgid "Are you sure you want to run %s?" -msgstr "Êtes vous sûr de vouloir lancer %s ?" +msgstr "Êtes-vous sûr de vouloir lancer %s ?" #: lib/tools.tcl:110 #, tcl-format @@ -2412,7 +2402,7 @@ msgstr "Lancement de : %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "L'outil a terminé avec succès : %s" #: lib/tools.tcl:151 @@ -2422,11 +2412,11 @@ msgstr "L'outil a échoué : %s" #: lib/tools_dlg.tcl:22 msgid "Add Tool" -msgstr "Ajouter outil" +msgstr "Ajouter un outil" #: lib/tools_dlg.tcl:28 msgid "Add New Tool Command" -msgstr "Ajouter nouvelle commande d'outil" +msgstr "Ajouter une nouvelle commande d'outil" #: lib/tools_dlg.tcl:33 msgid "Add globally" @@ -2438,7 +2428,7 @@ msgstr "Détails sur l'outil" #: lib/tools_dlg.tcl:48 msgid "Use '/' separators to create a submenu tree:" -msgstr "Utiliser les séparateurs '/' pour créer un arbre de sous menus :" +msgstr "Utiliser les séparateurs '/' pour créer un arbre de sous-menus :" #: lib/tools_dlg.tcl:61 msgid "Command:" @@ -2462,7 +2452,7 @@ msgstr "Ne pas montrer la fenêtre de sortie des commandes" #: lib/tools_dlg.tcl:97 msgid "Run only if a diff is selected ($FILENAME not empty)" -msgstr "Lancer seulement si un diff est selectionné ($FILENAME non vide)" +msgstr "Lancer seulement si un diff est sélectionné ($FILENAME non vide)" #: lib/tools_dlg.tcl:121 msgid "Please supply a name for the tool." @@ -2479,7 +2469,7 @@ msgid "" "Could not add tool:\n" "%s" msgstr "" -"Impossible d'ajouter l'outil:\n" +"Impossible d'ajouter l'outil :\n" "%s" #: lib/tools_dlg.tcl:190 diff --git a/git-gui/po/git-gui.pot b/git-gui/po/git-gui.pot index 15aea0dc64..53b7d3634d 100644 --- a/git-gui/po/git-gui.pot +++ b/git-gui/po/git-gui.pot @@ -753,13 +753,6 @@ msgstr "" msgid "The following branches are not completely merged into %s:" msgstr "" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2220,7 +2213,7 @@ msgstr "" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "" #: lib/tools.tcl:151 diff --git a/git-gui/po/hu.po b/git-gui/po/hu.po index f761b64152..0f87bc1cbe 100644 --- a/git-gui/po/hu.po +++ b/git-gui/po/hu.po @@ -776,16 +776,6 @@ msgstr "Mindig (Ne legyen merge teszt.)" msgid "The following branches are not completely merged into %s:" msgstr "A következÅ‘ branchek nem teljesen lettek merge-ölve ebbe: %s:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"A törölt branchek visszaállÃtása bonyolult. \n" -"\n" -" Biztosan törli a kiválasztott brancheket?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2399,7 +2389,7 @@ msgstr "Futtatás: %s..." #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "Az eszköz sikeresen befejezÅ‘dött: %s" #: lib/tools.tcl:151 diff --git a/git-gui/po/it.po b/git-gui/po/it.po index 294e595887..762632c22f 100644 --- a/git-gui/po/it.po +++ b/git-gui/po/it.po @@ -778,16 +778,6 @@ msgstr "Sempre (Non effettuare verifiche di fusione)." msgid "The following branches are not completely merged into %s:" msgstr "I rami seguenti non sono stati fusi completamente in %s:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"Ricomporre rami cancellati può essere complicato. \n" -"\n" -" Eliminare i rami selezionati?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2418,7 +2408,7 @@ msgstr "Eseguo: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "Il programma esterno è terminato con successo: %s" #: lib/tools.tcl:151 diff --git a/git-gui/po/ja.po b/git-gui/po/ja.po index 09d60bef74..63c4695103 100644 --- a/git-gui/po/ja.po +++ b/git-gui/po/ja.po @@ -773,16 +773,6 @@ msgstr "ç„¡æ¡ä»¶(マージテストã—ãªã„)" msgid "The following branches are not completely merged into %s:" msgstr "以下ã®ãƒ–ランãƒã¯ %s ã«å®Œå…¨ã«ãƒžãƒ¼ã‚¸ã•ã‚Œã¦ã„ã¾ã›ã‚“:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"ブランãƒã‚’削除ã™ã‚‹ã¨å…ƒã«æˆ»ã™ã®ã¯å›°é›£ã§ã™ã€‚ \n" -"\n" -" é¸æŠžã—ãŸãƒ–ランãƒã‚’削除ã—ã¾ã™ã‹ï¼Ÿ" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2382,7 +2372,7 @@ msgstr "実行ä¸: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "ツールãŒå®Œäº†ã—ã¾ã—ãŸ: %s" #: lib/tools.tcl:151 diff --git a/git-gui/po/nb.po b/git-gui/po/nb.po index 1c5137d84c..6de93c28c2 100644 --- a/git-gui/po/nb.po +++ b/git-gui/po/nb.po @@ -761,16 +761,6 @@ msgstr "Alltid (Ikke utfør sammenslÃ¥ingstest.)" msgid "The following branches are not completely merged into %s:" msgstr "Følgende grener er ikke fullstendig slÃ¥tt sammen med %s:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"Gjenoppretting av fjernede grener er vanskelig. \n" -"\n" -" Fjern valgte grener?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2331,7 +2321,7 @@ msgstr "Kjører: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "Verktøyet ble fullført med suksess: %s" #: lib/tools.tcl:151 diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po index db55b3e0a6..0ffc4a418f 100644 --- a/git-gui/po/ru.po +++ b/git-gui/po/ru.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-03-14 07:18+0100\n" +"POT-Creation-Date: 2008-12-08 08:31-0800\n" "PO-Revision-Date: 2007-10-22 22:30-0200\n" "Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n" "Language-Team: Russian Translation <git@vger.kernel.org>\n" @@ -15,33 +15,33 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744 -#: git-gui.sh:763 +#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847 +#: git-gui.sh:866 msgid "git-gui: fatal error" msgstr "git-gui: критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°" -#: git-gui.sh:593 +#: git-gui.sh:689 #, tcl-format msgid "Invalid font specified in %s:" msgstr "Ð’ %s уÑтановлен неверный шрифт:" -#: git-gui.sh:620 +#: git-gui.sh:723 msgid "Main Font" msgstr "Шрифт интерфейÑа" -#: git-gui.sh:621 +#: git-gui.sh:724 msgid "Diff/Console Font" msgstr "Шрифт конÑоли и изменений (diff)" -#: git-gui.sh:635 +#: git-gui.sh:738 msgid "Cannot find git in PATH." msgstr "git не найден в PATH." -#: git-gui.sh:662 +#: git-gui.sh:765 msgid "Cannot parse Git version string:" msgstr "Ðевозможно раÑпознать Ñтроку верÑии Git: " -#: git-gui.sh:680 +#: git-gui.sh:783 #, tcl-format msgid "" "Git version cannot be determined.\n" @@ -53,384 +53,451 @@ msgid "" "Assume '%s' is version 1.5.0?\n" msgstr "" "Ðевозможно определить верÑию Git\n" +"\n" "%s указывает на верÑию '%s'.\n" "\n" "Ð´Ð»Ñ %s требуетÑÑ Ð²ÐµÑ€ÑÐ¸Ñ Git, Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1.5.0\n" "\n" "ПринÑÑ‚ÑŒ '%s' как верÑию 1.5.0?\n" -#: git-gui.sh:918 +#: git-gui.sh:1062 msgid "Git directory not found:" msgstr "Каталог Git не найден:" -#: git-gui.sh:925 +#: git-gui.sh:1069 msgid "Cannot move to top of working directory:" msgstr "Ðевозможно перейти к корню рабочего каталога репозиториÑ: " -#: git-gui.sh:932 +#: git-gui.sh:1076 msgid "Cannot use funny .git directory:" -msgstr "Каталог.git иÑпорчен: " +msgstr "Каталог .git иÑпорчен: " -#: git-gui.sh:937 +#: git-gui.sh:1081 msgid "No working directory" msgstr "ОтÑутÑтвует рабочий каталог" -#: git-gui.sh:1084 lib/checkout_op.tcl:283 +#: git-gui.sh:1247 lib/checkout_op.tcl:305 msgid "Refreshing file status..." msgstr "Обновление информации о ÑоÑтоÑнии файлов..." -#: git-gui.sh:1149 +#: git-gui.sh:1303 msgid "Scanning for modified files ..." msgstr "ПоиÑк измененных файлов..." -#: git-gui.sh:1324 lib/browser.tcl:246 +#: git-gui.sh:1367 +msgid "Calling prepare-commit-msg hook..." +msgstr "Вызов программы поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ prepare-commit-msg..." + +#: git-gui.sh:1384 +msgid "Commit declined by prepare-commit-msg hook." +msgstr "Сохранение прервано программой поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ prepare-commit-msg" + +#: git-gui.sh:1542 lib/browser.tcl:246 msgid "Ready." msgstr "Готово." -#: git-gui.sh:1590 +#: git-gui.sh:1819 msgid "Unmodified" msgstr "Ðе изменено" -#: git-gui.sh:1592 +#: git-gui.sh:1821 msgid "Modified, not staged" msgstr "Изменено, не подготовлено" -#: git-gui.sh:1593 git-gui.sh:1598 +#: git-gui.sh:1822 git-gui.sh:1830 msgid "Staged for commit" msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ" -#: git-gui.sh:1594 git-gui.sh:1599 +#: git-gui.sh:1823 git-gui.sh:1831 msgid "Portions staged for commit" msgstr "ЧаÑти, подготовленные Ð´Ð»Ñ ÑохранениÑ" -#: git-gui.sh:1595 git-gui.sh:1600 +#: git-gui.sh:1824 git-gui.sh:1832 msgid "Staged for commit, missing" msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ, отÑутÑтвует" -#: git-gui.sh:1597 +#: git-gui.sh:1826 +msgid "File type changed, not staged" +msgstr "Тип файла изменён, не подготовлено" + +#: git-gui.sh:1827 +msgid "File type changed, staged" +msgstr "Тип файла изменён, подготовлено" + +#: git-gui.sh:1829 msgid "Untracked, not staged" msgstr "Ðе отÑлеживаетÑÑ, не подготовлено" -#: git-gui.sh:1602 +#: git-gui.sh:1834 msgid "Missing" msgstr "ОтÑутÑтвует" -#: git-gui.sh:1603 +#: git-gui.sh:1835 msgid "Staged for removal" msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ" -#: git-gui.sh:1604 +#: git-gui.sh:1836 msgid "Staged for removal, still present" msgstr "Подготовлено Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ, еще не удалено" -#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609 +#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841 +#: git-gui.sh:1842 git-gui.sh:1843 msgid "Requires merge resolution" -msgstr "ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта при объединении" +msgstr "ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта при ÑлиÑнии" -#: git-gui.sh:1644 +#: git-gui.sh:1878 msgid "Starting gitk... please wait..." -msgstr "ЗапуÑкаетÑÑ gitk... пожалуйÑта, ждите..." +msgstr "ЗапуÑкаетÑÑ gitk... Подождите, пожалуйÑта..." -#: git-gui.sh:1653 -#, tcl-format -msgid "" -"Unable to start gitk:\n" -"\n" -"%s does not exist" -msgstr "" -"Ðе удалоÑÑŒ запуÑтить gitk:\n" -"\n" -"%s не ÑущеÑтвует" +#: git-gui.sh:1887 +msgid "Couldn't find gitk in PATH" +msgstr "gitk не найден в PATH." -#: git-gui.sh:1860 lib/choose_repository.tcl:36 +#: git-gui.sh:2280 lib/choose_repository.tcl:36 msgid "Repository" msgstr "Репозиторий" -#: git-gui.sh:1861 +#: git-gui.sh:2281 msgid "Edit" msgstr "Редактировать" -#: git-gui.sh:1863 lib/choose_rev.tcl:561 +#: git-gui.sh:2283 lib/choose_rev.tcl:561 msgid "Branch" msgstr "Ветвь" -#: git-gui.sh:1866 lib/choose_rev.tcl:548 +#: git-gui.sh:2286 lib/choose_rev.tcl:548 msgid "Commit@@noun" msgstr "СоÑтоÑние" -#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167 +#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168 msgid "Merge" -msgstr "Объединить" +msgstr "СлиÑние" -#: git-gui.sh:1870 lib/choose_rev.tcl:557 +#: git-gui.sh:2290 lib/choose_rev.tcl:557 msgid "Remote" msgstr "Внешние репозитории" -#: git-gui.sh:1879 +#: git-gui.sh:2293 +msgid "Tools" +msgstr "Ð’Ñпомогательные операции" + +#: git-gui.sh:2302 +msgid "Explore Working Copy" +msgstr "ПроÑмотр рабочего каталога" + +#: git-gui.sh:2307 msgid "Browse Current Branch's Files" msgstr "ПроÑмотреть файлы текущей ветви" -#: git-gui.sh:1883 +#: git-gui.sh:2311 msgid "Browse Branch Files..." msgstr "Показать файлы ветви..." -#: git-gui.sh:1888 +#: git-gui.sh:2316 msgid "Visualize Current Branch's History" -msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹ ветви наглÑдно" +msgstr "Показать иÑторию текущей ветви" -#: git-gui.sh:1892 +#: git-gui.sh:2320 msgid "Visualize All Branch History" -msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ñех ветвей наглÑдно" +msgstr "Показать иÑторию вÑех ветвей" -#: git-gui.sh:1899 +#: git-gui.sh:2327 #, tcl-format msgid "Browse %s's Files" msgstr "Показать файлы ветви %s" -#: git-gui.sh:1901 +#: git-gui.sh:2329 #, tcl-format msgid "Visualize %s's History" -msgstr "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²ÐµÑ‚Ð²Ð¸ %s наглÑдно" +msgstr "Показать иÑторию ветви %s" -#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67 +#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67 msgid "Database Statistics" msgstr "СтатиÑтика базы данных" -#: git-gui.sh:1909 lib/database.tcl:34 +#: git-gui.sh:2337 lib/database.tcl:34 msgid "Compress Database" msgstr "Сжать базу данных" -#: git-gui.sh:1912 +#: git-gui.sh:2340 msgid "Verify Database" msgstr "Проверить базу данных" -#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7 +#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7 #: lib/shortcut.tcl:39 lib/shortcut.tcl:71 msgid "Create Desktop Icon" msgstr "Создать Ñрлык на рабочем Ñтоле" -#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185 +#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191 msgid "Quit" msgstr "Выход" -#: git-gui.sh:1939 +#: git-gui.sh:2371 msgid "Undo" msgstr "Отменить" -#: git-gui.sh:1942 +#: git-gui.sh:2374 msgid "Redo" msgstr "Повторить" -#: git-gui.sh:1946 git-gui.sh:2443 +#: git-gui.sh:2378 git-gui.sh:2937 msgid "Cut" msgstr "Вырезать" -#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614 +#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096 #: lib/console.tcl:69 msgid "Copy" msgstr "Копировать" -#: git-gui.sh:1952 git-gui.sh:2449 +#: git-gui.sh:2384 git-gui.sh:2943 msgid "Paste" msgstr "Ð’Ñтавить" -#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26 +#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26 #: lib/remote_branch_delete.tcl:38 msgid "Delete" msgstr "Удалить" -#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71 +#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71 msgid "Select All" msgstr "Выделить вÑе" -#: git-gui.sh:1968 +#: git-gui.sh:2400 msgid "Create..." msgstr "Создать..." -#: git-gui.sh:1974 +#: git-gui.sh:2406 msgid "Checkout..." msgstr "Перейти..." -#: git-gui.sh:1980 +#: git-gui.sh:2412 msgid "Rename..." msgstr "Переименовать..." -#: git-gui.sh:1985 git-gui.sh:2085 +#: git-gui.sh:2417 msgid "Delete..." msgstr "Удалить..." -#: git-gui.sh:1990 +#: git-gui.sh:2422 msgid "Reset..." msgstr "СброÑить..." -#: git-gui.sh:2002 git-gui.sh:2389 +#: git-gui.sh:2432 +msgid "Done" +msgstr "Завершено" + +#: git-gui.sh:2434 +msgid "Commit@@verb" +msgstr "Сохранить" + +#: git-gui.sh:2443 git-gui.sh:2878 msgid "New Commit" msgstr "Ðовое ÑоÑтоÑние" -#: git-gui.sh:2010 git-gui.sh:2396 +#: git-gui.sh:2451 git-gui.sh:2885 msgid "Amend Last Commit" msgstr "ИÑправить поÑледнее ÑоÑтоÑние" -#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99 +#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99 msgid "Rescan" msgstr "Перечитать" -#: git-gui.sh:2025 +#: git-gui.sh:2467 msgid "Stage To Commit" msgstr "Подготовить Ð´Ð»Ñ ÑохранениÑ" -#: git-gui.sh:2031 +#: git-gui.sh:2473 msgid "Stage Changed Files To Commit" msgstr "Подготовить измененные файлы Ð´Ð»Ñ ÑохранениÑ" -#: git-gui.sh:2037 +#: git-gui.sh:2479 msgid "Unstage From Commit" msgstr "Убрать из подготовленного" -#: git-gui.sh:2042 lib/index.tcl:395 +#: git-gui.sh:2484 lib/index.tcl:410 msgid "Revert Changes" msgstr "Отменить изменениÑ" -#: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467 -msgid "Sign Off" -msgstr "ПодпиÑать" +#: git-gui.sh:2491 git-gui.sh:3083 +msgid "Show Less Context" +msgstr "Меньше контекÑта" -#: git-gui.sh:2053 git-gui.sh:2372 -msgid "Commit@@verb" -msgstr "Сохранить" +#: git-gui.sh:2495 git-gui.sh:3087 +msgid "Show More Context" +msgstr "Больше контекÑта" + +#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961 +msgid "Sign Off" +msgstr "Ð’Ñтавить Signed-off-by" -#: git-gui.sh:2064 +#: git-gui.sh:2518 msgid "Local Merge..." -msgstr "Локальное объединение..." +msgstr "Локальное ÑлиÑние..." -#: git-gui.sh:2069 +#: git-gui.sh:2523 msgid "Abort Merge..." -msgstr "Прервать объединение..." +msgstr "Прервать ÑлиÑние..." + +#: git-gui.sh:2535 git-gui.sh:2575 +msgid "Add..." +msgstr "Добавить..." -#: git-gui.sh:2081 +#: git-gui.sh:2539 msgid "Push..." msgstr "Отправить..." -#: git-gui.sh:2092 lib/choose_repository.tcl:41 -msgid "Apple" -msgstr "" +#: git-gui.sh:2543 +msgid "Delete Branch..." +msgstr "Удалить ветвь..." -#: git-gui.sh:2095 git-gui.sh:2117 lib/about.tcl:14 -#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50 +#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14 +#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53 #, tcl-format msgid "About %s" msgstr "О %s" -#: git-gui.sh:2099 +#: git-gui.sh:2557 msgid "Preferences..." msgstr "ÐаÑтройки..." -#: git-gui.sh:2107 git-gui.sh:2639 +#: git-gui.sh:2565 git-gui.sh:3129 msgid "Options..." msgstr "ÐаÑтройки..." -#: git-gui.sh:2113 lib/choose_repository.tcl:47 +#: git-gui.sh:2576 +msgid "Remove..." +msgstr "Удалить..." + +#: git-gui.sh:2585 lib/choose_repository.tcl:50 msgid "Help" msgstr "Помощь" -#: git-gui.sh:2154 +#: git-gui.sh:2611 msgid "Online Documentation" msgstr "Ð”Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ñ Ð² интернете" -#: git-gui.sh:2238 +#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56 +msgid "Show SSH Key" +msgstr "Показать ключ SSH" + +#: git-gui.sh:2721 #, tcl-format msgid "fatal: cannot stat path %s: No such file or directory" msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: %s: нет такого файла или каталога" -#: git-gui.sh:2271 +#: git-gui.sh:2754 msgid "Current Branch:" msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ:" -#: git-gui.sh:2292 +#: git-gui.sh:2775 msgid "Staged Changes (Will Commit)" msgstr "Подготовлено (будет Ñохранено)" -#: git-gui.sh:2312 +#: git-gui.sh:2795 msgid "Unstaged Changes" msgstr "Изменено (не будет Ñохранено)" -#: git-gui.sh:2362 +#: git-gui.sh:2845 msgid "Stage Changed" msgstr "Подготовить вÑе" -#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182 +#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193 msgid "Push" msgstr "Отправить" -#: git-gui.sh:2408 +#: git-gui.sh:2899 msgid "Initial Commit Message:" msgstr "Комментарий к первому ÑоÑтоÑнию:" -#: git-gui.sh:2409 +#: git-gui.sh:2900 msgid "Amended Commit Message:" msgstr "Комментарий к иÑправленному ÑоÑтоÑнию:" -#: git-gui.sh:2410 +#: git-gui.sh:2901 msgid "Amended Initial Commit Message:" msgstr "Комментарий к иÑправленному первоначальному ÑоÑтоÑнию:" -#: git-gui.sh:2411 +#: git-gui.sh:2902 msgid "Amended Merge Commit Message:" -msgstr "Комментарий к иÑправленному объединению:" +msgstr "Комментарий к иÑправленному ÑлиÑнию:" -#: git-gui.sh:2412 +#: git-gui.sh:2903 msgid "Merge Commit Message:" -msgstr "Комментарий к объединению:" +msgstr "Комментарий к ÑлиÑнию:" -#: git-gui.sh:2413 +#: git-gui.sh:2904 msgid "Commit Message:" msgstr "Комментарий к ÑоÑтоÑнию:" -#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73 +#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73 msgid "Copy All" msgstr "Копировать вÑе" -#: git-gui.sh:2483 lib/blame.tcl:107 +#: git-gui.sh:2977 lib/blame.tcl:104 msgid "File:" msgstr "Файл:" -#: git-gui.sh:2589 -msgid "Apply/Reverse Hunk" -msgstr "Применить/Убрать изменение" - -#: git-gui.sh:2595 -msgid "Show Less Context" -msgstr "Меньше контекÑта" - -#: git-gui.sh:2602 -msgid "Show More Context" -msgstr "Больше контекÑта" - -#: git-gui.sh:2610 +#: git-gui.sh:3092 msgid "Refresh" msgstr "Обновить" -#: git-gui.sh:2631 +#: git-gui.sh:3113 msgid "Decrease Font Size" msgstr "Уменьшить размер шрифта" -#: git-gui.sh:2635 +#: git-gui.sh:3117 msgid "Increase Font Size" msgstr "Увеличить размер шрифта" -#: git-gui.sh:2646 +#: git-gui.sh:3125 lib/blame.tcl:281 +msgid "Encoding" +msgstr "Кодировка" + +#: git-gui.sh:3136 +msgid "Apply/Reverse Hunk" +msgstr "Применить/Убрать изменение" + +#: git-gui.sh:3141 +msgid "Apply/Reverse Line" +msgstr "Применить/Убрать Ñтроку" + +#: git-gui.sh:3151 +msgid "Run Merge Tool" +msgstr "ЗапуÑтить программу ÑлиÑниÑ" + +#: git-gui.sh:3156 +msgid "Use Remote Version" +msgstr "ВзÑÑ‚ÑŒ внешнюю верÑию" + +#: git-gui.sh:3160 +msgid "Use Local Version" +msgstr "ВзÑÑ‚ÑŒ локальную верÑию" + +#: git-gui.sh:3164 +msgid "Revert To Base" +msgstr "Отменить изменениÑ" + +#: git-gui.sh:3183 msgid "Unstage Hunk From Commit" msgstr "Ðе ÑохранÑÑ‚ÑŒ чаÑÑ‚ÑŒ" -#: git-gui.sh:2648 +#: git-gui.sh:3184 +msgid "Unstage Line From Commit" +msgstr "Убрать Ñтроку из подготовленного" + +#: git-gui.sh:3186 msgid "Stage Hunk For Commit" msgstr "Подготовить чаÑÑ‚ÑŒ Ð´Ð»Ñ ÑохранениÑ" -#: git-gui.sh:2667 +#: git-gui.sh:3187 +msgid "Stage Line For Commit" +msgstr "Подготовить Ñтроку Ð´Ð»Ñ ÑохранениÑ" + +#: git-gui.sh:3210 msgid "Initializing..." msgstr "ИнициализациÑ..." -#: git-gui.sh:2762 +#: git-gui.sh:3315 #, tcl-format msgid "" "Possible environment issues exist.\n" @@ -447,7 +514,7 @@ msgstr "" "запущенными из %s\n" "\n" -#: git-gui.sh:2792 +#: git-gui.sh:3345 msgid "" "\n" "This is due to a known issue with the\n" @@ -457,7 +524,7 @@ msgstr "" "Ðто извеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð° Ñ Tcl,\n" "раÑпроÑтранÑемым Cygwin." -#: git-gui.sh:2797 +#: git-gui.sh:3350 #, tcl-format msgid "" "\n" @@ -478,64 +545,108 @@ msgstr "" msgid "git-gui - a graphical user interface for Git." msgstr "git-gui - графичеÑкий пользовательÑкий Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ðº Git." -#: lib/blame.tcl:77 +#: lib/blame.tcl:72 msgid "File Viewer" msgstr "ПроÑмотр файла" -#: lib/blame.tcl:81 +#: lib/blame.tcl:78 msgid "Commit:" msgstr "Сохраненное ÑоÑтоÑние:" -#: lib/blame.tcl:264 +#: lib/blame.tcl:271 msgid "Copy Commit" msgstr "Скопировать SHA-1" -#: lib/blame.tcl:384 +#: lib/blame.tcl:275 +msgid "Find Text..." +msgstr "Ðайти текÑÑ‚..." + +#: lib/blame.tcl:284 +msgid "Do Full Copy Detection" +msgstr "ПровеÑти полный поиÑк копий" + +#: lib/blame.tcl:288 +msgid "Show History Context" +msgstr "Показать иÑторичеÑкий контекÑÑ‚" + +#: lib/blame.tcl:291 +msgid "Blame Parent Commit" +msgstr "РаÑÑмотреть ÑоÑтоÑние предка" + +#: lib/blame.tcl:450 #, tcl-format msgid "Reading %s..." msgstr "Чтение %s..." -#: lib/blame.tcl:488 +#: lib/blame.tcl:557 msgid "Loading copy/move tracking annotations..." msgstr "Загрузка аннотации копирований/переименований..." -#: lib/blame.tcl:508 +#: lib/blame.tcl:577 msgid "lines annotated" msgstr "Ñтрок прокомментировано" -#: lib/blame.tcl:689 +#: lib/blame.tcl:769 msgid "Loading original location annotations..." msgstr "Загрузка аннотаций первоначального Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð°..." -#: lib/blame.tcl:692 +#: lib/blame.tcl:772 msgid "Annotation complete." msgstr "ÐÐ½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð°." -#: lib/blame.tcl:746 +#: lib/blame.tcl:802 +msgid "Busy" +msgstr "ЗанÑÑ‚" + +#: lib/blame.tcl:803 +msgid "Annotation process is already running." +msgstr "ÐÐ½Ð½Ð¾Ñ‚Ð°Ñ†Ð¸Ñ ÑƒÐ¶Ðµ запущена" + +#: lib/blame.tcl:842 +msgid "Running thorough copy detection..." +msgstr "Выполнение полного поиÑка копий..." + +#: lib/blame.tcl:910 msgid "Loading annotation..." msgstr "Загрузка аннотации..." -#: lib/blame.tcl:802 +#: lib/blame.tcl:963 msgid "Author:" msgstr "Ðвтор:" -#: lib/blame.tcl:806 +#: lib/blame.tcl:967 msgid "Committer:" msgstr "Сохранил:" -#: lib/blame.tcl:811 +#: lib/blame.tcl:972 msgid "Original File:" msgstr "ИÑходный файл:" -#: lib/blame.tcl:925 +#: lib/blame.tcl:1020 +msgid "Cannot find HEAD commit:" +msgstr "Ðевозможно найти текущее ÑоÑтоÑние:" + +#: lib/blame.tcl:1075 +msgid "Cannot find parent commit:" +msgstr "Ðевозможно найти ÑоÑтоÑние предка:" + +#: lib/blame.tcl:1090 +msgid "Unable to display parent" +msgstr "Ðе могу показать предка" + +#: lib/blame.tcl:1091 lib/diff.tcl:297 +msgid "Error loading diff:" +msgstr "Ошибка загрузки изменений:" + +#: lib/blame.tcl:1231 msgid "Originally By:" msgstr "ИÑточник:" -#: lib/blame.tcl:931 +#: lib/blame.tcl:1237 msgid "In File:" msgstr "Файл:" -#: lib/blame.tcl:936 +#: lib/blame.tcl:1242 msgid "Copied Or Moved Here By:" msgstr "Скопировано/перемещено в:" @@ -549,16 +660,18 @@ msgstr "Перейти" #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282 -#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171 -#: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97 +#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172 +#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42 +#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352 +#: lib/transport.tcl:108 msgid "Cancel" -msgstr "Отменить" +msgstr "Отмена" -#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 +#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328 msgid "Revision" msgstr "ВерÑиÑ" -#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242 +#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280 msgid "Options" msgstr "ÐаÑтройки" @@ -578,7 +691,7 @@ msgstr "Создание ветви" msgid "Create New Branch" msgstr "Создать новую ветвь" -#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371 +#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377 msgid "Create" msgstr "Создать" @@ -586,7 +699,7 @@ msgstr "Создать" msgid "Branch Name" msgstr "Ðазвание ветви" -#: lib/branch_create.tcl:43 +#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50 msgid "Name:" msgstr "Ðазвание:" @@ -610,7 +723,7 @@ msgstr "Ðет" msgid "Fast Forward Only" msgstr "Только Fast Forward" -#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514 +#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536 msgid "Reset" msgstr "СброÑ" @@ -650,26 +763,16 @@ msgstr "Локальные ветви" #: lib/branch_delete.tcl:52 msgid "Delete Only If Merged Into" -msgstr "Удалить только в Ñлучае, еÑли было объединение Ñ" +msgstr "Удалить только в Ñлучае, еÑли было ÑлиÑние Ñ" #: lib/branch_delete.tcl:54 msgid "Always (Do not perform merge test.)" -msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку на объединение)" +msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку на ÑлиÑние)" #: lib/branch_delete.tcl:103 #, tcl-format msgid "The following branches are not completely merged into %s:" -msgstr "Следующие ветви объединены Ñ %s не полноÑтью:" - -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"ВоÑÑтанавливать удаленные ветви Ñложно. \n" -"\n" -" Удалить выбранные ветви?" +msgstr "Ветви, которые не полноÑтью ÑливаютÑÑ Ñ %s:" #: lib/branch_delete.tcl:141 #, tcl-format @@ -700,7 +803,7 @@ msgstr "Ðовое название:" msgid "Please select a branch to rename." msgstr "Укажите ветвь Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¸Ð¼ÐµÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ." -#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179 +#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201 #, tcl-format msgid "Branch '%s' already exists." msgstr "Ветвь '%s' уже ÑущеÑтвует." @@ -731,32 +834,38 @@ msgstr "[Ðа уровень выше]" msgid "Browse Branch Files" msgstr "Показать файлы ветви" -#: lib/browser.tcl:278 lib/choose_repository.tcl:387 -#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484 -#: lib/choose_repository.tcl:987 +#: lib/browser.tcl:278 lib/choose_repository.tcl:394 +#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491 +#: lib/choose_repository.tcl:995 msgid "Browse" msgstr "Показать" -#: lib/checkout_op.tcl:79 +#: lib/checkout_op.tcl:84 #, tcl-format msgid "Fetching %s from %s" msgstr "Получение %s из %s " -#: lib/checkout_op.tcl:127 +#: lib/checkout_op.tcl:132 #, tcl-format msgid "fatal: Cannot resolve %s" msgstr "критичеÑÐºÐ°Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ°: невозможно разрешить %s" -#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31 +#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31 +#: lib/sshkey.tcl:53 msgid "Close" msgstr "Закрыть" -#: lib/checkout_op.tcl:169 +#: lib/checkout_op.tcl:174 #, tcl-format msgid "Branch '%s' does not exist." msgstr "Ветвь '%s' не ÑущеÑтвует " -#: lib/checkout_op.tcl:206 +#: lib/checkout_op.tcl:193 +#, tcl-format +msgid "Failed to configure simplified git-pull for '%s'." +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ ÑƒÐ¿Ñ€Ð¾Ñ‰Ñ‘Ð½Ð½Ð¾Ð¹ конфигурации git pull Ð´Ð»Ñ '%s'." + +#: lib/checkout_op.tcl:228 #, tcl-format msgid "" "Branch '%s' already exists.\n" @@ -767,23 +876,23 @@ msgstr "" "Ветвь '%s' уже ÑущеÑтвует.\n" "\n" "Она не может быть прокручена(fast-forward) к %s.\n" -"ТребуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ." +"ТребуетÑÑ ÑлиÑние." -#: lib/checkout_op.tcl:220 +#: lib/checkout_op.tcl:242 #, tcl-format msgid "Merge strategy '%s' not supported." -msgstr "Ð¡Ñ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ '%s' не поддерживаетÑÑ." +msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ ÑÑ‚Ñ€Ð°Ñ‚ÐµÐ³Ð¸Ñ ÑлиÑниÑ: '%s'." -#: lib/checkout_op.tcl:239 +#: lib/checkout_op.tcl:261 #, tcl-format msgid "Failed to update '%s'." msgstr "Ðе удалоÑÑŒ обновить '%s'." -#: lib/checkout_op.tcl:251 +#: lib/checkout_op.tcl:273 msgid "Staging area (index) is already locked." msgstr "Ð Ð°Ð±Ð¾Ñ‡Ð°Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑŒ заблокирована другим процеÑÑом." -#: lib/checkout_op.tcl:266 +#: lib/checkout_op.tcl:288 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -799,30 +908,30 @@ msgstr "" "\n" "Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" -#: lib/checkout_op.tcl:322 +#: lib/checkout_op.tcl:344 #, tcl-format msgid "Updating working directory to '%s'..." msgstr "Обновление рабочего каталога из '%s'..." -#: lib/checkout_op.tcl:323 +#: lib/checkout_op.tcl:345 msgid "files checked out" msgstr "файлы извлечены" -#: lib/checkout_op.tcl:353 +#: lib/checkout_op.tcl:375 #, tcl-format msgid "Aborted checkout of '%s' (file level merging is required)." -msgstr "Прерван переход на '%s' (требуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ на уровне файлов)" +msgstr "Прерван переход на '%s' (требуетÑÑ ÑлиÑние ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²)" -#: lib/checkout_op.tcl:354 +#: lib/checkout_op.tcl:376 msgid "File level merge required." -msgstr "ТребуетÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ðµ на уровне файлов." +msgstr "ТребуетÑÑ ÑлиÑние ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð²." -#: lib/checkout_op.tcl:358 +#: lib/checkout_op.tcl:380 #, tcl-format msgid "Staying on branch '%s'." msgstr "Ветвь '%s' оÑтаетÑÑ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ¹." -#: lib/checkout_op.tcl:429 +#: lib/checkout_op.tcl:451 msgid "" "You are no longer on a local branch.\n" "\n" @@ -834,30 +943,30 @@ msgstr "" "ЕÑли вы хотите Ñнова вернутьÑÑ Ðº какой-нибудь ветви, Ñоздайте ее ÑейчаÑ, " "Ð½Ð°Ñ‡Ð¸Ð½Ð°Ñ Ñ 'Текущего отÑоединенного ÑоÑтоÑниÑ'." -#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450 +#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472 #, tcl-format msgid "Checked out '%s'." msgstr "Ветвь '%s' Ñделана текущей." -#: lib/checkout_op.tcl:478 +#: lib/checkout_op.tcl:500 #, tcl-format msgid "Resetting '%s' to '%s' will lose the following commits:" msgstr "Ð¡Ð±Ñ€Ð¾Ñ '%s' в '%s' приведет к потере Ñледующих Ñохраненных ÑоÑтоÑний: " -#: lib/checkout_op.tcl:500 +#: lib/checkout_op.tcl:522 msgid "Recovering lost commits may not be easy." msgstr "ВоÑÑтановить потерÑнные Ñохраненные ÑоÑтоÑÐ½Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ Ñложно." -#: lib/checkout_op.tcl:505 +#: lib/checkout_op.tcl:527 #, tcl-format msgid "Reset '%s'?" msgstr "СброÑить '%s'?" -#: lib/checkout_op.tcl:510 lib/merge.tcl:163 +#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343 msgid "Visualize" msgstr "ÐаглÑдно" -#: lib/checkout_op.tcl:578 +#: lib/checkout_op.tcl:600 #, tcl-format msgid "" "Failed to set current branch.\n" @@ -900,224 +1009,228 @@ msgstr "" #: lib/choose_repository.tcl:28 msgid "Git Gui" -msgstr "" +msgstr "Git Gui" -#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376 +#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382 msgid "Create New Repository" msgstr "Создать новый репозиторий" -#: lib/choose_repository.tcl:87 +#: lib/choose_repository.tcl:93 msgid "New..." msgstr "Ðовый..." -#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460 +#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465 msgid "Clone Existing Repository" msgstr "Склонировать ÑущеÑтвующий репозиторий" -#: lib/choose_repository.tcl:100 +#: lib/choose_repository.tcl:106 msgid "Clone..." msgstr "Склонировать..." -#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976 +#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983 msgid "Open Existing Repository" msgstr "Выбрать ÑущеÑтвующий репозиторий" -#: lib/choose_repository.tcl:113 +#: lib/choose_repository.tcl:119 msgid "Open..." msgstr "Открыть..." -#: lib/choose_repository.tcl:126 +#: lib/choose_repository.tcl:132 msgid "Recent Repositories" msgstr "Ðедавние репозитории" -#: lib/choose_repository.tcl:132 +#: lib/choose_repository.tcl:138 msgid "Open Recent Repository:" msgstr "Открыть поÑледний репозиторий" -#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303 -#: lib/choose_repository.tcl:310 +#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309 +#: lib/choose_repository.tcl:316 #, tcl-format msgid "Failed to create repository %s:" msgstr "Ðе удалоÑÑŒ Ñоздать репозиторий %s:" -#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478 +#: lib/choose_repository.tcl:387 msgid "Directory:" msgstr "Каталог:" -#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537 -#: lib/choose_repository.tcl:1011 +#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544 +#: lib/choose_repository.tcl:1017 msgid "Git Repository" msgstr "Репозиторий" -#: lib/choose_repository.tcl:437 +#: lib/choose_repository.tcl:442 #, tcl-format msgid "Directory %s already exists." msgstr "Каталог '%s' уже ÑущеÑтвует." -#: lib/choose_repository.tcl:441 +#: lib/choose_repository.tcl:446 #, tcl-format msgid "File %s already exists." msgstr "Файл '%s' уже ÑущеÑтвует." -#: lib/choose_repository.tcl:455 +#: lib/choose_repository.tcl:460 msgid "Clone" msgstr "Склонировать" -#: lib/choose_repository.tcl:468 -msgid "URL:" -msgstr "СÑылка:" +#: lib/choose_repository.tcl:473 +msgid "Source Location:" +msgstr "ИÑходное положение:" + +#: lib/choose_repository.tcl:484 +msgid "Target Directory:" +msgstr "Каталог назначениÑ:" -#: lib/choose_repository.tcl:489 +#: lib/choose_repository.tcl:496 msgid "Clone Type:" msgstr "Тип клона:" -#: lib/choose_repository.tcl:495 +#: lib/choose_repository.tcl:502 msgid "Standard (Fast, Semi-Redundant, Hardlinks)" msgstr "Стандартный (БыÑтрый, полуизбыточный, \"жеÑткие\" ÑÑылки)" -#: lib/choose_repository.tcl:501 +#: lib/choose_repository.tcl:508 msgid "Full Copy (Slower, Redundant Backup)" msgstr "ÐŸÐ¾Ð»Ð½Ð°Ñ ÐºÐ¾Ð¿Ð¸Ñ (Медленный, Ñоздает резервную копию)" -#: lib/choose_repository.tcl:507 +#: lib/choose_repository.tcl:514 msgid "Shared (Fastest, Not Recommended, No Backup)" msgstr "Общий (Самый быÑтрый, не рекомендуетÑÑ, без резервной копии)" -#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590 -#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806 -#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025 +#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597 +#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813 +#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031 #, tcl-format msgid "Not a Git repository: %s" msgstr "Каталог не ÑвлÑетÑÑ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸ÐµÐ¼: %s" -#: lib/choose_repository.tcl:579 +#: lib/choose_repository.tcl:586 msgid "Standard only available for local repository." msgstr "Стандартный клон возможен только Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ репозиториÑ." -#: lib/choose_repository.tcl:583 +#: lib/choose_repository.tcl:590 msgid "Shared only available for local repository." msgstr "Общий клон возможен только Ð´Ð»Ñ Ð»Ð¾ÐºÐ°Ð»ÑŒÐ½Ð¾Ð³Ð¾ репозиториÑ." -#: lib/choose_repository.tcl:604 +#: lib/choose_repository.tcl:611 #, tcl-format msgid "Location %s already exists." msgstr "Путь '%s' уже ÑущеÑтвует." -#: lib/choose_repository.tcl:615 +#: lib/choose_repository.tcl:622 msgid "Failed to configure origin" msgstr "Ðе могу Ñконфигурировать иÑходный репозиторий." -#: lib/choose_repository.tcl:627 +#: lib/choose_repository.tcl:634 msgid "Counting objects" msgstr "Считаю объекты" -#: lib/choose_repository.tcl:628 +#: lib/choose_repository.tcl:635 msgid "buckets" msgstr "" -#: lib/choose_repository.tcl:652 +#: lib/choose_repository.tcl:659 #, tcl-format msgid "Unable to copy objects/info/alternates: %s" msgstr "Ðе могу Ñкопировать objects/info/alternates: %s" -#: lib/choose_repository.tcl:688 +#: lib/choose_repository.tcl:695 #, tcl-format msgid "Nothing to clone from %s." msgstr "Ðечего клонировать Ñ %s." -#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904 -#: lib/choose_repository.tcl:916 +#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911 +#: lib/choose_repository.tcl:923 msgid "The 'master' branch has not been initialized." msgstr "Ðе инициализирована ветвь 'master'." -#: lib/choose_repository.tcl:703 +#: lib/choose_repository.tcl:710 msgid "Hardlinks are unavailable. Falling back to copying." -msgstr "\"ЖеÑткие ÑÑылки\" не доÑтупны. Буду иÑпользовать копирование." +msgstr "\"ЖеÑткие ÑÑылки\" недоÑтупны. Будет иÑпользовано копирование." -#: lib/choose_repository.tcl:715 +#: lib/choose_repository.tcl:722 #, tcl-format msgid "Cloning from %s" msgstr "Клонирование %s" -#: lib/choose_repository.tcl:746 +#: lib/choose_repository.tcl:753 msgid "Copying objects" msgstr "Копирование objects" -#: lib/choose_repository.tcl:747 +#: lib/choose_repository.tcl:754 msgid "KiB" msgstr "КБ" -#: lib/choose_repository.tcl:771 +#: lib/choose_repository.tcl:778 #, tcl-format msgid "Unable to copy object: %s" msgstr "Ðе могу Ñкопировать объект: %s" -#: lib/choose_repository.tcl:781 +#: lib/choose_repository.tcl:788 msgid "Linking objects" msgstr "Создание ÑÑылок на objects" -#: lib/choose_repository.tcl:782 +#: lib/choose_repository.tcl:789 msgid "objects" msgstr "объекты" -#: lib/choose_repository.tcl:790 +#: lib/choose_repository.tcl:797 #, tcl-format msgid "Unable to hardlink object: %s" msgstr "Ðе могу \"жеÑтко ÑвÑзать\" объект: %s" -#: lib/choose_repository.tcl:845 +#: lib/choose_repository.tcl:852 msgid "Cannot fetch branches and objects. See console output for details." msgstr "" "Ðе могу получить ветви и объекты. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." -#: lib/choose_repository.tcl:856 +#: lib/choose_repository.tcl:863 msgid "Cannot fetch tags. See console output for details." msgstr "Ðе могу получить метки. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." -#: lib/choose_repository.tcl:880 +#: lib/choose_repository.tcl:887 msgid "Cannot determine HEAD. See console output for details." msgstr "Ðе могу определить HEAD. Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð½Ð° конÑоли." -#: lib/choose_repository.tcl:889 +#: lib/choose_repository.tcl:896 #, tcl-format msgid "Unable to cleanup %s" msgstr "Ðе могу очиÑтить %s" -#: lib/choose_repository.tcl:895 +#: lib/choose_repository.tcl:902 msgid "Clone failed." msgstr "Клонирование не удалоÑÑŒ." -#: lib/choose_repository.tcl:902 +#: lib/choose_repository.tcl:909 msgid "No default branch obtained." msgstr "Ðе было получено ветви по умолчанию." -#: lib/choose_repository.tcl:913 +#: lib/choose_repository.tcl:920 #, tcl-format msgid "Cannot resolve %s as a commit." msgstr "Ðе могу раÑпознать %s как ÑоÑтоÑние." -#: lib/choose_repository.tcl:925 +#: lib/choose_repository.tcl:932 msgid "Creating working directory" msgstr "Создаю рабочий каталог" -#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127 -#: lib/index.tcl:193 +#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128 +#: lib/index.tcl:196 msgid "files" msgstr "файлов" -#: lib/choose_repository.tcl:955 +#: lib/choose_repository.tcl:962 msgid "Initial file checkout failed." msgstr "Ðе удалоÑÑŒ получить начальное ÑоÑтоÑние файлов репозиториÑ." -#: lib/choose_repository.tcl:971 +#: lib/choose_repository.tcl:978 msgid "Open" msgstr "Открыть" -#: lib/choose_repository.tcl:981 +#: lib/choose_repository.tcl:988 msgid "Repository:" msgstr "Репозиторий:" -#: lib/choose_repository.tcl:1031 +#: lib/choose_repository.tcl:1037 #, tcl-format msgid "Failed to open repository %s:" msgstr "Ðе удалоÑÑŒ открыть репозиторий %s:" @@ -1140,7 +1253,7 @@ msgstr "Ветвь ÑлежениÑ" #: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538 msgid "Tag" -msgstr "Таг" +msgstr "Метка" #: lib/choose_rev.tcl:317 #, tcl-format @@ -1182,24 +1295,24 @@ msgid "" "completed. You cannot amend the prior commit unless you first abort the " "current merge activity.\n" msgstr "" -"Ðевозможно иÑправить ÑоÑтоÑние во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ.\n" +"Ðевозможно иÑправить ÑоÑтоÑние во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ð¸ ÑлиÑниÑ.\n" "\n" -"Текущее объединение не завершено. Ðевозможно иÑправить предыдущее " -"Ñохраненное ÑоÑтоÑние не Ð¿Ñ€ÐµÑ€Ñ‹Ð²Ð°Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐµ объединение.\n" +"Текущее ÑлиÑние не завершено. Ðевозможно иÑправить предыдущее " +"Ñохраненное ÑоÑтоÑние, не Ð¿Ñ€ÐµÑ€Ñ‹Ð²Ð°Ñ Ñту операцию.\n" -#: lib/commit.tcl:49 +#: lib/commit.tcl:48 msgid "Error loading commit data for amend:" msgstr "Ошибка при загрузке данных Ð´Ð»Ñ Ð¸ÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ:" -#: lib/commit.tcl:76 +#: lib/commit.tcl:75 msgid "Unable to obtain your identity:" msgstr "Ðевозможно получить информацию об авторÑтве:" -#: lib/commit.tcl:81 +#: lib/commit.tcl:80 msgid "Invalid GIT_COMMITTER_IDENT:" msgstr "Ðеверный GIT_COMMITTER_IDENT:" -#: lib/commit.tcl:133 +#: lib/commit.tcl:132 msgid "" "Last scanned state does not match repository state.\n" "\n" @@ -1215,7 +1328,7 @@ msgstr "" "\n" "Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" -#: lib/commit.tcl:154 +#: lib/commit.tcl:155 #, tcl-format msgid "" "Unmerged files cannot be committed.\n" @@ -1223,12 +1336,12 @@ msgid "" "File %s has merge conflicts. You must resolve them and stage the file " "before committing.\n" msgstr "" -"ÐÐµÐ»ÑŒÐ·Ñ Ñохранить необъединенные файлы.\n" +"ÐÐµÐ»ÑŒÐ·Ñ Ñохранить файлы Ñ Ð½ÐµÐ·Ð°Ð²ÐµÑ€ÑˆÑ‘Ð½Ð½Ð¾Ð¹ операцей ÑлиÑниÑ.\n" "\n" -"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт объединениÑ. Разрешите конфликт и добавьте к " +"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт ÑлиÑниÑ. Разрешите конфликт и добавьте к " "подготовленным файлам перед Ñохранением.\n" -#: lib/commit.tcl:162 +#: lib/commit.tcl:163 #, tcl-format msgid "" "Unknown file state %s detected.\n" @@ -1239,7 +1352,7 @@ msgstr "" "\n" "Файл %s не может быть Ñохранен данной программой.\n" -#: lib/commit.tcl:170 +#: lib/commit.tcl:171 msgid "" "No changes to commit.\n" "\n" @@ -1249,7 +1362,7 @@ msgstr "" "\n" "Подготовьте Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один файл до ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñохраненного ÑоÑтоÑниÑ.\n" -#: lib/commit.tcl:183 +#: lib/commit.tcl:186 msgid "" "Please supply a commit message.\n" "\n" @@ -1267,45 +1380,45 @@ msgstr "" "- Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ñтрока пуÑтаÑ\n" "- оÑтавшиеÑÑ Ñтроки: опишите, что дают ваши изменениÑ.\n" -#: lib/commit.tcl:207 +#: lib/commit.tcl:210 #, tcl-format msgid "warning: Tcl does not support encoding '%s'." msgstr "предупреждение: Tcl не поддерживает кодировку '%s'." -#: lib/commit.tcl:221 +#: lib/commit.tcl:226 msgid "Calling pre-commit hook..." msgstr "Вызов программы поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ pre-commit..." -#: lib/commit.tcl:236 +#: lib/commit.tcl:241 msgid "Commit declined by pre-commit hook." msgstr "Сохранение прервано программой поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ pre-commit" -#: lib/commit.tcl:259 +#: lib/commit.tcl:264 msgid "Calling commit-msg hook..." msgstr "Вызов программы поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ commit-msg..." -#: lib/commit.tcl:274 +#: lib/commit.tcl:279 msgid "Commit declined by commit-msg hook." msgstr "Сохранение прервано программой поддержки Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ commit-msg" -#: lib/commit.tcl:287 +#: lib/commit.tcl:292 msgid "Committing changes..." msgstr "Сохранение изменений..." -#: lib/commit.tcl:303 +#: lib/commit.tcl:308 msgid "write-tree failed:" msgstr "Программа write-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" -#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368 +#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373 msgid "Commit failed." msgstr "Сохранить ÑоÑтоÑние не удалоÑÑŒ." -#: lib/commit.tcl:321 +#: lib/commit.tcl:326 #, tcl-format msgid "Commit %s appears to be corrupt" msgstr "СоÑтоÑние %s выглÑдит поврежденным" -#: lib/commit.tcl:326 +#: lib/commit.tcl:331 msgid "" "No changes to commit.\n" "\n" @@ -1315,23 +1428,23 @@ msgid "" msgstr "" "ОтÑутÑтвуют Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ.\n" "\n" -"Ðи один файл не был изменен и не было объединениÑ.\n" +"Ðи один файл не был изменен и не было ÑлиÑниÑ.\n" "\n" "Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки запуÑтитÑÑ Ð¿ÐµÑ€ÐµÑ‡Ð¸Ñ‚Ñ‹Ð²Ð°Ð½Ð¸Ðµ репозиториÑ.\n" -#: lib/commit.tcl:333 +#: lib/commit.tcl:338 msgid "No changes to commit." msgstr "ОтуÑтвуют Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ." -#: lib/commit.tcl:347 +#: lib/commit.tcl:352 msgid "commit-tree failed:" msgstr "Программа commit-tree завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" -#: lib/commit.tcl:367 +#: lib/commit.tcl:372 msgid "update-ref failed:" msgstr "Программа update-ref завершилаÑÑŒ Ñ Ð¾ÑˆÐ¸Ð±ÐºÐ¾Ð¹:" -#: lib/commit.tcl:454 +#: lib/commit.tcl:460 #, tcl-format msgid "Created commit %s: %s" msgstr "Создано ÑоÑтоÑние %s: %s " @@ -1406,7 +1519,7 @@ msgstr "" msgid "Invalid date from Git: %s" msgstr "ÐÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð´Ð°Ñ‚Ð° в репозитории: %s" -#: lib/diff.tcl:42 +#: lib/diff.tcl:59 #, tcl-format msgid "" "No differences detected.\n" @@ -1428,40 +1541,101 @@ msgstr "" "\n" "Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð±ÑƒÐ´ÐµÑ‚ запущено перечитывание репозиториÑ, чтобы найти подобные файлы." -#: lib/diff.tcl:81 +#: lib/diff.tcl:99 #, tcl-format msgid "Loading diff of %s..." msgstr "Загрузка изменений в %s..." -#: lib/diff.tcl:114 lib/diff.tcl:184 +#: lib/diff.tcl:120 +msgid "" +"LOCAL: deleted\n" +"REMOTE:\n" +msgstr "" +"ЛОКÐЛЬÐО: удалён\n" +"Ð’ÐЕШÐИЙ:\n" + +#: lib/diff.tcl:125 +msgid "" +"REMOTE: deleted\n" +"LOCAL:\n" +msgstr "" +"Ð’ÐЕШÐИЙ: удалён\n" +"ЛОКÐЛЬÐО:\n" + +#: lib/diff.tcl:132 +msgid "LOCAL:\n" +msgstr "ЛОКÐЛЬÐО:\n" + +#: lib/diff.tcl:135 +msgid "REMOTE:\n" +msgstr "Ð’ÐЕШÐИЙ:\n" + +#: lib/diff.tcl:197 lib/diff.tcl:296 #, tcl-format msgid "Unable to display %s" msgstr "Ðе могу показать %s" -#: lib/diff.tcl:115 +#: lib/diff.tcl:198 msgid "Error loading file:" msgstr "Ошибка загрузки файла:" -#: lib/diff.tcl:122 +#: lib/diff.tcl:205 msgid "Git Repository (subproject)" msgstr "Репозиторий Git (подпроект)" -#: lib/diff.tcl:134 +#: lib/diff.tcl:217 msgid "* Binary file (not showing content)." msgstr "* Двоичный файл (Ñодержимое не показано)" -#: lib/diff.tcl:185 -msgid "Error loading diff:" -msgstr "Ошибка загрузки diff:" +#: lib/diff.tcl:222 +#, tcl-format +msgid "" +"* Untracked file is %d bytes.\n" +"* Showing only first %d bytes.\n" +msgstr "" +"* Размер неподготовленого файла %d байт.\n" +"* Показано первых %d байт.\n" + +#: lib/diff.tcl:228 +#, tcl-format +msgid "" +"\n" +"* Untracked file clipped here by %s.\n" +"* To see the entire file, use an external editor.\n" +msgstr "" +"\n" +"* Ðеподготовленый файл обрезан: %s.\n" +"* Чтобы увидеть веÑÑŒ файл, иÑпользуйте программу-редактор.\n" -#: lib/diff.tcl:303 +#: lib/diff.tcl:436 msgid "Failed to unstage selected hunk." msgstr "Ðе удалоÑÑŒ иÑключить выбранную чаÑÑ‚ÑŒ." -#: lib/diff.tcl:310 +#: lib/diff.tcl:443 msgid "Failed to stage selected hunk." msgstr "Ðе удалоÑÑŒ подготовить к Ñохранению выбранную чаÑÑ‚ÑŒ." +#: lib/diff.tcl:509 +msgid "Failed to unstage selected line." +msgstr "Ðе удалоÑÑŒ иÑключить выбранную Ñтроку." + +#: lib/diff.tcl:517 +msgid "Failed to stage selected line." +msgstr "Ðе удалоÑÑŒ подготовить к Ñохранению выбранную Ñтроку." + +#: lib/encoding.tcl:443 +msgid "Default" +msgstr "По умолчанию" + +#: lib/encoding.tcl:448 +#, tcl-format +msgid "System (%s)" +msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ (%s)" + +#: lib/encoding.tcl:459 lib/encoding.tcl:465 +msgid "Other" +msgstr "ДругаÑ" + #: lib/error.tcl:20 lib/error.tcl:114 msgid "error" msgstr "ошибка" @@ -1480,7 +1654,7 @@ msgstr "Ðе удалоÑÑŒ разблокировать индекÑ" #: lib/index.tcl:15 msgid "Index Error" -msgstr "Ошибка индекÑа" +msgstr "Ошибка в индекÑе" #: lib/index.tcl:21 msgid "" @@ -1498,50 +1672,59 @@ msgstr "Продолжить" msgid "Unlock Index" msgstr "Разблокировать индекÑ" -#: lib/index.tcl:282 +#: lib/index.tcl:287 #, tcl-format msgid "Unstaging %s from commit" msgstr "Удаление %s из подготовленного" -#: lib/index.tcl:313 +#: lib/index.tcl:326 msgid "Ready to commit." msgstr "Подготовлено Ð´Ð»Ñ ÑохранениÑ" -#: lib/index.tcl:326 +#: lib/index.tcl:339 #, tcl-format msgid "Adding %s" msgstr "Добавление %s..." -#: lib/index.tcl:381 +#: lib/index.tcl:396 #, tcl-format msgid "Revert changes in file %s?" msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файле %s?" -#: lib/index.tcl:383 +#: lib/index.tcl:398 #, tcl-format msgid "Revert changes in these %i files?" msgstr "Отменить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² %i файле(-ах)?" -#: lib/index.tcl:391 +#: lib/index.tcl:406 msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" "Любые изменениÑ, не подготовленные к Ñохранению, будут потерÑны при данной " "операции." -#: lib/index.tcl:394 +#: lib/index.tcl:409 msgid "Do Nothing" msgstr "Ðичего не делать" +#: lib/index.tcl:427 +msgid "Reverting selected files" +msgstr "Удаление изменений в выбраных файлах" + +#: lib/index.tcl:431 +#, tcl-format +msgid "Reverting %s" +msgstr "Отмена изменений в %s" + #: lib/merge.tcl:13 msgid "" "Cannot merge while amending.\n" "\n" "You must finish amending this commit before starting any type of merge.\n" msgstr "" -"Ðевозможно выполнить объединение во Ð²Ñ€ÐµÐ¼Ñ Ð¸ÑправлениÑ.\n" +"Ðевозможно выполнить ÑлиÑние во Ð²Ñ€ÐµÐ¼Ñ Ð¸ÑправлениÑ.\n" "\n" "Завершите иÑправление данного ÑоÑтоÑÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ выполнением операции " -"объединениÑ.\n" +"ÑлиÑниÑ.\n" #: lib/merge.tcl:27 msgid "" @@ -1559,7 +1742,7 @@ msgstr "" "\n" "Ðто будет Ñделано ÑÐµÐ¹Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑки.\n" -#: lib/merge.tcl:44 +#: lib/merge.tcl:45 #, tcl-format msgid "" "You are in the middle of a conflicted merge.\n" @@ -1569,14 +1752,14 @@ msgid "" "You must resolve them, stage the file, and commit to complete the current " "merge. Only then can you begin another merge.\n" msgstr "" -"Предыдущее объединение не завершено из-за конфликта.\n" +"Предыдущее ÑлиÑние не завершено из-за конфликта.\n" "\n" -"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт объединениÑ.\n" +"Ð”Ð»Ñ Ñ„Ð°Ð¹Ð»Ð° %s возник конфликт ÑлиÑниÑ.\n" "\n" "Разрешите конфликт, подготовьте файл и Ñохраните. Только поÑле Ñтого можно " -"начать Ñледующее объединение.\n" +"начать Ñледующее ÑлиÑние.\n" -#: lib/merge.tcl:54 +#: lib/merge.tcl:55 #, tcl-format msgid "" "You are in the middle of a change.\n" @@ -1590,36 +1773,37 @@ msgstr "" "\n" "Файл %s изменен.\n" "\n" -"Подготовьте и Ñохраните Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ началом объединениÑ. Ð’ Ñлучае " -"необходимоÑти Ñто позволит прервать операцию объединениÑ.\n" +"Подготовьте и Ñохраните Ð¸Ð·Ð¼ÐµÐ½Ð¸Ñ Ð¿ÐµÑ€ÐµÐ´ началом ÑлиÑниÑ. Ð’ Ñлучае " +"необходимоÑти Ñто позволит прервать операцию ÑлиÑниÑ.\n" -#: lib/merge.tcl:106 +#: lib/merge.tcl:107 #, tcl-format msgid "%s of %s" msgstr "%s из %s" -#: lib/merge.tcl:119 +#: lib/merge.tcl:120 +#, tcl-format msgid "Merging %s and %s..." -msgstr "Объединение %s и %s..." +msgstr "СлиÑние %s и %s..." -#: lib/merge.tcl:130 +#: lib/merge.tcl:131 msgid "Merge completed successfully." -msgstr "Объединение уÑпешно завершено." +msgstr "СлиÑние уÑпешно завершено." -#: lib/merge.tcl:132 +#: lib/merge.tcl:133 msgid "Merge failed. Conflict resolution is required." -msgstr "Ðе удалоÑÑŒ завершить объединение. ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта." +msgstr "Ðе удалоÑÑŒ завершить ÑлиÑние. ТребуетÑÑ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ðµ конфликта." -#: lib/merge.tcl:157 +#: lib/merge.tcl:158 #, tcl-format msgid "Merge Into %s" -msgstr "Объединить Ñ %s" +msgstr "СлиÑние Ñ %s" -#: lib/merge.tcl:176 +#: lib/merge.tcl:177 msgid "Revision To Merge" -msgstr "ВерÑÐ¸Ñ Ð´Ð»Ñ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ" +msgstr "ВерÑиÑ, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ провеÑти ÑлиÑние" -#: lib/merge.tcl:211 +#: lib/merge.tcl:212 msgid "" "Cannot abort while amending.\n" "\n" @@ -1629,7 +1813,7 @@ msgstr "" "\n" "Завершите текущее иÑправление Ñохраненного ÑоÑтоÑниÑ.\n" -#: lib/merge.tcl:221 +#: lib/merge.tcl:222 msgid "" "Abort merge?\n" "\n" @@ -1637,13 +1821,13 @@ msgid "" "\n" "Continue with aborting the current merge?" msgstr "" -"Прервать объединение?\n" +"Прервать операцию ÑлиÑниÑ?\n" "\n" -"Прерывание Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n" +"Прерывание Ñтой операции приведет к потере *ВСЕХ* неÑохраненных изменений.\n" "\n" "Продолжить?" -#: lib/merge.tcl:227 +#: lib/merge.tcl:228 msgid "" "Reset changes?\n" "\n" @@ -1651,130 +1835,346 @@ msgid "" "\n" "Continue with resetting the current changes?" msgstr "" -"Прервать объединение?\n" +"Прервать операцию ÑлиÑниÑ?\n" "\n" -"Прерывание Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ð²ÐµÐ´ÐµÑ‚ к потере *ВСЕХ* неÑохраненных изменений.\n" +"Прерывание Ñтой операции приведет к потере *ВСЕХ* неÑохраненных изменений.\n" "\n" "Продолжить?" -#: lib/merge.tcl:238 +#: lib/merge.tcl:239 msgid "Aborting" msgstr "Прерываю" -#: lib/merge.tcl:238 +#: lib/merge.tcl:239 msgid "files reset" msgstr "Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файлах отменены" -#: lib/merge.tcl:265 +#: lib/merge.tcl:267 msgid "Abort failed." msgstr "Прервать не удалоÑÑŒ." -#: lib/merge.tcl:267 +#: lib/merge.tcl:269 msgid "Abort completed. Ready." msgstr "Прервано." -#: lib/option.tcl:95 +#: lib/mergetool.tcl:8 +msgid "Force resolution to the base version?" +msgstr "ИÑпользовать базовую верÑию Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" + +#: lib/mergetool.tcl:9 +msgid "Force resolution to this branch?" +msgstr "ИÑпользовать верÑию Ñтой ветви Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" + +#: lib/mergetool.tcl:10 +msgid "Force resolution to the other branch?" +msgstr "ИÑпользовать верÑию другой ветви Ð´Ð»Ñ Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ ÐºÐ¾Ð½Ñ„Ð»Ð¸ÐºÑ‚Ð°?" + +#: lib/mergetool.tcl:14 +#, tcl-format +msgid "" +"Note that the diff shows only conflicting changes.\n" +"\n" +"%s will be overwritten.\n" +"\n" +"This operation can be undone only by restarting the merge." +msgstr "" +"Внимание! СпиÑок изменений показывает только конфликтующие отличиÑ.\n" +"\n" +"%s будет перепиÑан.\n" +"\n" +"Ðто дейÑтвие можно отменить только перезапуÑком операции ÑлиÑниÑ." + +#: lib/mergetool.tcl:45 +#, tcl-format +msgid "File %s seems to have unresolved conflicts, still stage?" +msgstr "" +"Файл %s кажетÑÑ Ñодержит необработаные конфликты. " +"Продолжить подготовку к Ñохранению?" + +#: lib/mergetool.tcl:60 +#, tcl-format +msgid "Adding resolution for %s" +msgstr "ДобавлÑÑŽ результат Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ %s" + +#: lib/mergetool.tcl:141 +msgid "Cannot resolve deletion or link conflicts using a tool" +msgstr "" +"Программа ÑлиÑÐ½Ð¸Ñ Ð½Ðµ обрабатывает конфликты Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸ÐµÐ¼ или учаÑтием ÑÑылок" + +#: lib/mergetool.tcl:146 +msgid "Conflict file does not exist" +msgstr "Конфликтующий файл не ÑущеÑтвует" + +#: lib/mergetool.tcl:264 +#, tcl-format +msgid "Not a GUI merge tool: '%s'" +msgstr "'%s' не ÑвлÑетÑÑ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð¾Ð¹ ÑлиÑниÑ" + +#: lib/mergetool.tcl:268 +#, tcl-format +msgid "Unsupported merge tool '%s'" +msgstr "ÐеизвеÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° ÑлиÑÐ½Ð¸Ñ '%s'" + +#: lib/mergetool.tcl:303 +msgid "Merge tool is already running, terminate it?" +msgstr "Программа ÑлиÑÐ½Ð¸Ñ ÑƒÐ¶Ðµ работает. Прервать?" + +#: lib/mergetool.tcl:323 +#, tcl-format +msgid "" +"Error retrieving versions:\n" +"%s" +msgstr "" +"Ошибка Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð²ÐµÑ€Ñий:\n" +"%s" + +#: lib/mergetool.tcl:343 +#, tcl-format +msgid "" +"Could not start the merge tool:\n" +"\n" +"%s" +msgstr "" +"Ошибка запуÑка программы ÑлиÑниÑ:\n" +"\n" +"%s" + +#: lib/mergetool.tcl:347 +msgid "Running merge tool..." +msgstr "ЗапуÑк программы ÑлиÑниÑ..." + +#: lib/mergetool.tcl:375 lib/mergetool.tcl:383 +msgid "Merge tool failed." +msgstr "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹ ÑлиÑниÑ." + +#: lib/option.tcl:11 +#, tcl-format +msgid "Invalid global encoding '%s'" +msgstr "Ошибка в глобальной уÑтановке кодировки '%s'" + +#: lib/option.tcl:19 +#, tcl-format +msgid "Invalid repo encoding '%s'" +msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ ÐºÐ¾Ð´Ð¸Ñ€Ð¾Ð²ÐºÐ° репозиториÑ: '%s'" + +#: lib/option.tcl:117 msgid "Restore Defaults" msgstr "ВоÑÑтановить наÑтройки по умолчанию" -#: lib/option.tcl:99 +#: lib/option.tcl:121 msgid "Save" msgstr "Сохранить" -#: lib/option.tcl:109 +#: lib/option.tcl:131 #, tcl-format msgid "%s Repository" -msgstr "Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ %s" +msgstr "Ð”Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ %s" -#: lib/option.tcl:110 +#: lib/option.tcl:132 msgid "Global (All Repositories)" msgstr "Общие (Ð´Ð»Ñ Ð²Ñех репозиториев)" -#: lib/option.tcl:116 +#: lib/option.tcl:138 msgid "User Name" msgstr "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" -#: lib/option.tcl:117 +#: lib/option.tcl:139 msgid "Email Address" msgstr "ÐÐ´Ñ€ÐµÑ Ñлектронной почты" -#: lib/option.tcl:119 +#: lib/option.tcl:141 msgid "Summarize Merge Commits" -msgstr "Суммарный комментарий при объединении" +msgstr "Суммарный комментарий при ÑлиÑнии" -#: lib/option.tcl:120 +#: lib/option.tcl:142 msgid "Merge Verbosity" -msgstr "Уровень детальноÑти Ñообщений при объединении" +msgstr "Уровень детальноÑти Ñообщений при ÑлиÑнии" -#: lib/option.tcl:121 +#: lib/option.tcl:143 msgid "Show Diffstat After Merge" -msgstr "Показать отчет об изменениÑÑ… поÑле объединениÑ" +msgstr "Показать отчет об изменениÑÑ… поÑле ÑлиÑниÑ" + +#: lib/option.tcl:144 +msgid "Use Merge Tool" +msgstr "ИÑпользовать Ð´Ð»Ñ ÑлиÑÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñƒ" -#: lib/option.tcl:123 +#: lib/option.tcl:146 msgid "Trust File Modification Timestamps" msgstr "ДоверÑÑ‚ÑŒ времени модификации файла" -#: lib/option.tcl:124 +#: lib/option.tcl:147 msgid "Prune Tracking Branches During Fetch" msgstr "ЧиÑтка ветвей ÑÐ»ÐµÐ¶ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ получении изменений" -#: lib/option.tcl:125 +#: lib/option.tcl:148 msgid "Match Tracking Branches" msgstr "Ð˜Ð¼Ñ Ð½Ð¾Ð²Ð¾Ð¹ ветви взÑÑ‚ÑŒ из имен ветвей ÑлежениÑ" -#: lib/option.tcl:126 +#: lib/option.tcl:149 +msgid "Blame Copy Only On Changed Files" +msgstr "ПоиÑк копий только в изменённых файлах" + +#: lib/option.tcl:150 +msgid "Minimum Letters To Blame Copy On" +msgstr "Минимальное количеÑтво Ñимволов Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка копий" + +#: lib/option.tcl:151 +msgid "Blame History Context Radius (days)" +msgstr "Ð Ð°Ð´Ð¸ÑƒÑ Ð¸ÑторичеÑкого контекÑта (в днÑÑ…)" + +#: lib/option.tcl:152 msgid "Number of Diff Context Lines" msgstr "ЧиÑло Ñтрок в контекÑте diff" -#: lib/option.tcl:127 +#: lib/option.tcl:153 msgid "Commit Message Text Width" -msgstr "Ширина ÐºÐ¾Ð¼Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ð¸Ñ Ðº ÑоÑтоÑнию:" +msgstr "Ширина текÑта комментариÑ" -#: lib/option.tcl:128 +#: lib/option.tcl:154 msgid "New Branch Name Template" msgstr "Шаблон Ð´Ð»Ñ Ð¸Ð¼ÐµÐ½Ð¸ новой ветви" -#: lib/option.tcl:192 +#: lib/option.tcl:155 +msgid "Default File Contents Encoding" +msgstr "Кодировка ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° по умолчанию" + +#: lib/option.tcl:203 +msgid "Change" +msgstr "Изменить" + +#: lib/option.tcl:230 msgid "Spelling Dictionary:" msgstr "Словарь Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ правопиÑаниÑ:" -#: lib/option.tcl:216 +#: lib/option.tcl:254 msgid "Change Font" -msgstr "Изменить шрифт" +msgstr "Изменить" -#: lib/option.tcl:220 +#: lib/option.tcl:258 #, tcl-format msgid "Choose %s" msgstr "Выберите %s" # carbon copy -#: lib/option.tcl:226 +#: lib/option.tcl:264 msgid "pt." -msgstr "" +msgstr "pt." -#: lib/option.tcl:240 +#: lib/option.tcl:278 msgid "Preferences" msgstr "ÐаÑтройки" -#: lib/option.tcl:275 +#: lib/option.tcl:314 msgid "Failed to completely save options:" msgstr "Ðе удалоÑÑŒ полноÑтью Ñохранить наÑтройки:" +#: lib/remote.tcl:163 +msgid "Remove Remote" +msgstr "Удалить ÑÑылку на внешний репозиторий" + +#: lib/remote.tcl:168 +msgid "Prune from" +msgstr "ЧиÑтка" + +#: lib/remote.tcl:173 +msgid "Fetch from" +msgstr "Получение из" + +#: lib/remote.tcl:215 +msgid "Push to" +msgstr "Отправить" + +#: lib/remote_add.tcl:19 +msgid "Add Remote" +msgstr "ЗарегиÑтрировать внешний репозиторий" + +#: lib/remote_add.tcl:24 +msgid "Add New Remote" +msgstr "Добавить внешний репозиторий" + +#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36 +msgid "Add" +msgstr "" + +#: lib/remote_add.tcl:37 +msgid "Remote Details" +msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ внешнем репозитории" + +#: lib/remote_add.tcl:50 +msgid "Location:" +msgstr "Положение:" + +#: lib/remote_add.tcl:62 +msgid "Further Action" +msgstr "Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ" + +#: lib/remote_add.tcl:65 +msgid "Fetch Immediately" +msgstr "Скачать Ñразу" + +#: lib/remote_add.tcl:71 +msgid "Initialize Remote Repository and Push" +msgstr "Инициализировать внешний репозиторий и отправить" + +#: lib/remote_add.tcl:77 +msgid "Do Nothing Else Now" +msgstr "Больше ничего не делать" + +#: lib/remote_add.tcl:101 +msgid "Please supply a remote name." +msgstr "Укажите название внешнего репозиториÑ." + +#: lib/remote_add.tcl:114 +#, tcl-format +msgid "'%s' is not an acceptable remote name." +msgstr "ÐедопуÑтимое название внешнего Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ '%s'." + +#: lib/remote_add.tcl:125 +#, tcl-format +msgid "Failed to add remote '%s' of location '%s'." +msgstr "Ðе удалоÑÑŒ добавить '%s' из '%s'. " + +#: lib/remote_add.tcl:133 lib/transport.tcl:6 +#, tcl-format +msgid "fetch %s" +msgstr "получение %s" + +#: lib/remote_add.tcl:134 +#, tcl-format +msgid "Fetching the %s" +msgstr "Получение %s" + +#: lib/remote_add.tcl:157 +#, tcl-format +msgid "Do not know how to initialize repository at location '%s'." +msgstr "Ðевозможно инициалировать репозиторий в '%s'." + +#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63 +#: lib/transport.tcl:81 +#, tcl-format +msgid "push %s" +msgstr "отправить %s" + +#: lib/remote_add.tcl:164 +#, tcl-format +msgid "Setting up the %s (at %s)" +msgstr "ÐаÑтройка %s (в %s)" + #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34 -msgid "Delete Remote Branch" -msgstr "Удалить внешнюю ветвь" +msgid "Delete Branch Remotely" +msgstr "Удаление ветви во внешнем репозитории" #: lib/remote_branch_delete.tcl:47 msgid "From Repository" msgstr "Из репозиториÑ" -#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123 +#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134 msgid "Remote:" msgstr "внешний:" -#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138 -msgid "Arbitrary URL:" -msgstr "по указанному URL:" +#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149 +msgid "Arbitrary Location:" +msgstr "Указаное положение:" #: lib/remote_branch_delete.tcl:84 msgid "Branches" @@ -1786,15 +2186,15 @@ msgstr "Удалить только в Ñлучае, еÑли" #: lib/remote_branch_delete.tcl:111 msgid "Merged Into:" -msgstr "Объединено Ñ:" +msgstr "СлиÑние Ñ:" #: lib/remote_branch_delete.tcl:119 msgid "Always (Do not perform merge checks)" -msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку объединений)" +msgstr "Ð’Ñегда (не выполнÑÑ‚ÑŒ проверку на ÑлиÑние)" #: lib/remote_branch_delete.tcl:152 msgid "A branch is required for 'Merged Into'." -msgstr "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ 'Объединено Ñ' требуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ ветвь." +msgstr "Ð”Ð»Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ 'СлиÑние Ñ' требуетÑÑ ÑƒÐºÐ°Ð·Ð°Ñ‚ÑŒ ветвь." #: lib/remote_branch_delete.tcl:184 #, tcl-format @@ -1803,7 +2203,8 @@ msgid "" "\n" " - %s" msgstr "" -"Следующие ветви объединены Ñ %s не полноÑтью:\n" +"Следующие ветви могут быть объединены Ñ %s при помощи операции ÑлиÑниÑ:\n" +"\n" " - %s" #: lib/remote_branch_delete.tcl:189 @@ -1812,7 +2213,7 @@ msgid "" "One or more of the merge tests failed because you have not fetched the " "necessary commits. Try fetching from %s first." msgstr "" -"Один или неÑколько теÑтов на объединение не прошли, потому что Ð’Ñ‹ не " +"Ðекоторые теÑÑ‚Ñ‹ на ÑлиÑние не прошли, потому что Ð’Ñ‹ не " "получили необходимые ÑоÑтоÑниÑ. ПопытайтеÑÑŒ получить их из %s." #: lib/remote_branch_delete.tcl:207 @@ -1843,17 +2244,21 @@ msgstr "Ðе указан репозиторий." msgid "Scanning %s..." msgstr "Перечитывание %s... " -#: lib/remote.tcl:165 -msgid "Prune from" -msgstr "ЧиÑтка" +#: lib/search.tcl:21 +msgid "Find:" +msgstr "ПоиÑк:" -#: lib/remote.tcl:170 -msgid "Fetch from" -msgstr "Получение из" +#: lib/search.tcl:23 +msgid "Next" +msgstr "Дальше" -#: lib/remote.tcl:213 -msgid "Push to" -msgstr "Отправить" +#: lib/search.tcl:24 +msgid "Prev" +msgstr "Обратно" + +#: lib/search.tcl:25 +msgid "Case-Sensitive" +msgstr "Игн. большие/маленькие" #: lib/shortcut.tcl:20 lib/shortcut.tcl:61 msgid "Cannot write shortcut:" @@ -1888,27 +2293,192 @@ msgstr "Программа проверки правопиÑÐ°Ð½Ð¸Ñ Ð½Ðµ Ñмо msgid "Unrecognized spell checker" msgstr "ÐераÑÐ¿Ð¾Ð·Ð½Ð°Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° проверки правопиÑаниÑ" -#: lib/spellcheck.tcl:180 +#: lib/spellcheck.tcl:186 msgid "No Suggestions" msgstr "ИÑправлений не найдено" -#: lib/spellcheck.tcl:381 +#: lib/spellcheck.tcl:388 msgid "Unexpected EOF from spell checker" msgstr "Программа проверки правопиÑÐ°Ð½Ð¸Ñ Ð¿Ñ€ÐµÑ€Ð²Ð°Ð»Ð° передачу данных" -#: lib/spellcheck.tcl:385 +#: lib/spellcheck.tcl:392 msgid "Spell Checker Failed" msgstr "Ошибка проверки правопиÑаниÑ" +#: lib/sshkey.tcl:31 +msgid "No keys found." +msgstr "Ключ не найден" + +#: lib/sshkey.tcl:34 +#, tcl-format +msgid "Found a public key in: %s" +msgstr "Публичный ключ из %s" + +#: lib/sshkey.tcl:40 +msgid "Generate Key" +msgstr "Создать ключ" + +#: lib/sshkey.tcl:56 +msgid "Copy To Clipboard" +msgstr "Скопировать в буфер обмена" + +#: lib/sshkey.tcl:70 +msgid "Your OpenSSH Public Key" +msgstr "Ваш публичный ключ OpenSSH" + +#: lib/sshkey.tcl:78 +msgid "Generating..." +msgstr "Создание..." + +#: lib/sshkey.tcl:84 +#, tcl-format +msgid "" +"Could not start ssh-keygen:\n" +"\n" +"%s" +msgstr "" +"Ошибка запуÑка ssh-keygen:\n" +"\n" +"%s" + +#: lib/sshkey.tcl:111 +msgid "Generation failed." +msgstr "Ключ не Ñоздан." + +#: lib/sshkey.tcl:118 +msgid "Generation succeded, but no keys found." +msgstr "Создание ключа завершилоÑÑŒ, но результат не был найден" + +#: lib/sshkey.tcl:121 +#, tcl-format +msgid "Your key is in: %s" +msgstr "Ваш ключ находитÑÑ Ð²: %s" + #: lib/status_bar.tcl:83 #, tcl-format msgid "%s ... %*i of %*i %s (%3i%%)" msgstr "%s ... %*i из %*i %s (%3i%%)" -#: lib/transport.tcl:6 +#: lib/tools.tcl:75 #, tcl-format -msgid "fetch %s" -msgstr "получение %s" +msgid "Running %s requires a selected file." +msgstr "ЗапуÑк %s требует выбранного файла." + +#: lib/tools.tcl:90 +#, tcl-format +msgid "Are you sure you want to run %s?" +msgstr "ДейÑтвительно запуÑтить %s?" + +#: lib/tools.tcl:110 +#, tcl-format +msgid "Tool: %s" +msgstr "Ð’ÑÐ¿Ð¾Ð¼Ð¾Ð³Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ: %s" + +#: lib/tools.tcl:111 +#, tcl-format +msgid "Running: %s" +msgstr "Выполнение: %s" + +#: lib/tools.tcl:149 +#, tcl-format +msgid "Tool completed succesfully: %s" +msgstr "Программа %s уÑпешно завершилаÑÑŒ." + +#: lib/tools.tcl:151 +#, tcl-format +msgid "Tool failed: %s" +msgstr "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹: %s" + +#: lib/tools_dlg.tcl:22 +msgid "Add Tool" +msgstr "Добавить вÑпомогательную операцию" + +#: lib/tools_dlg.tcl:28 +msgid "Add New Tool Command" +msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÑÐ¿Ð¾Ð¼Ð¾Ð³Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ" + +#: lib/tools_dlg.tcl:33 +msgid "Add globally" +msgstr "Добавить Ð´Ð»Ñ Ð²Ñех репозиториев" + +#: lib/tools_dlg.tcl:45 +msgid "Tool Details" +msgstr "ОпиÑание вÑпомогательной операции" + +#: lib/tools_dlg.tcl:48 +msgid "Use '/' separators to create a submenu tree:" +msgstr "ИÑпольуйте '/' Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð´Ð¼ÐµÐ½ÑŽ" + +#: lib/tools_dlg.tcl:61 +msgid "Command:" +msgstr "Команда:" + +#: lib/tools_dlg.tcl:74 +msgid "Show a dialog before running" +msgstr "Показать диалог перед запуÑком" + +#: lib/tools_dlg.tcl:80 +msgid "Ask the user to select a revision (sets $REVISION)" +msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð½Ð° выбор верÑии (уÑтанавливает $REVISION)" + +#: lib/tools_dlg.tcl:85 +msgid "Ask the user for additional arguments (sets $ARGS)" +msgstr "Ð—Ð°Ð¿Ñ€Ð¾Ñ Ð´Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ñ… аргументов (уÑтанавливает $ARGS)" + +#: lib/tools_dlg.tcl:92 +msgid "Don't show the command output window" +msgstr "Ðе показывать окно вывода команды" + +#: lib/tools_dlg.tcl:97 +msgid "Run only if a diff is selected ($FILENAME not empty)" +msgstr "ЗапуÑк только еÑли показан ÑпиÑок изменений ($FILENAME не пуÑто)" + +#: lib/tools_dlg.tcl:121 +msgid "Please supply a name for the tool." +msgstr "Укажите название вÑпомогательной операции." + +#: lib/tools_dlg.tcl:129 +#, tcl-format +msgid "Tool '%s' already exists." +msgstr "Ð’ÑÐ¿Ð¾Ð¼Ð¾Ð³Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ '%s' уже ÑущеÑтвует." + +#: lib/tools_dlg.tcl:151 +#, tcl-format +msgid "" +"Could not add tool:\n" +"%s" +msgstr "" +"Ошибка Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹:\n" +"%s" + +#: lib/tools_dlg.tcl:190 +msgid "Remove Tool" +msgstr "Удалить программу" + +#: lib/tools_dlg.tcl:196 +msgid "Remove Tool Commands" +msgstr "Удалить команды программы" + +#: lib/tools_dlg.tcl:200 +msgid "Remove" +msgstr "Удалить" + +#: lib/tools_dlg.tcl:236 +msgid "(Blue denotes repository-local tools)" +msgstr "(Синим выделены программы локальные репозиторию)" + +#: lib/tools_dlg.tcl:297 +#, tcl-format +msgid "Run Command: %s" +msgstr "ЗапуÑк команды: %s" + +#: lib/tools_dlg.tcl:311 +msgid "Arguments" +msgstr "Ðргументы" + +#: lib/tools_dlg.tcl:348 +msgid "OK" +msgstr "OK" #: lib/transport.tcl:7 #, tcl-format @@ -1926,48 +2496,46 @@ msgstr "чиÑтка внешнего %s" msgid "Pruning tracking branches deleted from %s" msgstr "ЧиÑтка ветвей ÑлежениÑ, удаленных из %s" -#: lib/transport.tcl:25 lib/transport.tcl:71 -#, tcl-format -msgid "push %s" -msgstr "отправить %s" - #: lib/transport.tcl:26 #, tcl-format msgid "Pushing changes to %s" msgstr "Отправка изменений в %s " -#: lib/transport.tcl:72 +#: lib/transport.tcl:64 +#, tcl-format +msgid "Mirroring to %s" +msgstr "Точное копирование в %s" + +#: lib/transport.tcl:82 #, tcl-format msgid "Pushing %s %s to %s" msgstr "Отправка %s %s в %s" -#: lib/transport.tcl:89 +#: lib/transport.tcl:100 msgid "Push Branches" msgstr "Отправить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² ветвÑÑ…" -#: lib/transport.tcl:103 +#: lib/transport.tcl:114 msgid "Source Branches" msgstr "ИÑходные ветви" -#: lib/transport.tcl:120 +#: lib/transport.tcl:131 msgid "Destination Repository" msgstr "Репозиторий назначениÑ" -#: lib/transport.tcl:158 +#: lib/transport.tcl:169 msgid "Transfer Options" msgstr "ÐаÑтройки отправки" -#: lib/transport.tcl:160 +#: lib/transport.tcl:171 msgid "Force overwrite existing branch (may discard changes)" msgstr "Ðамеренно перепиÑать ÑущеÑтвующую ветвь (возможна Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹)" -#: lib/transport.tcl:164 +#: lib/transport.tcl:175 msgid "Use thin pack (for slow network connections)" msgstr "ИÑпользовать thin pack (Ð´Ð»Ñ Ð¼ÐµÐ´Ð»ÐµÐ½Ð½Ñ‹Ñ… Ñетевых подключений)" -#: lib/transport.tcl:168 +#: lib/transport.tcl:179 msgid "Include tags" -msgstr "Передать таги" +msgstr "Передать метки" -#~ msgid "Next >" -#~ msgstr "Дальше >" diff --git a/git-gui/po/sv.po b/git-gui/po/sv.po index 167654c709..c1535f94e8 100644 --- a/git-gui/po/sv.po +++ b/git-gui/po/sv.po @@ -780,16 +780,6 @@ msgstr "Alltid (utför inte sammanslagningstest)." msgid "The following branches are not completely merged into %s:" msgstr "Följande grenar är inte till fullo sammanslagna med %s:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"Det är svÃ¥rt att Ã¥terställa borttagna grenar.\n" -"\n" -" Ta bort valda grenar?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" @@ -2398,7 +2388,7 @@ msgstr "Exekverar: %s" #: lib/tools.tcl:149 #, tcl-format -msgid "Tool completed succesfully: %s" +msgid "Tool completed successfully: %s" msgstr "Verktyget avslutades framgÃ¥ngsrikt: %s" #: lib/tools.tcl:151 diff --git a/git-gui/po/zh_cn.po b/git-gui/po/zh_cn.po index d2c6866671..91c1be23c2 100644 --- a/git-gui/po/zh_cn.po +++ b/git-gui/po/zh_cn.po @@ -676,16 +676,6 @@ msgstr "总是åˆå¹¶ (ä¸ä½œåˆå¹¶æµ‹è¯•.)" msgid "The following branches are not completely merged into %s:" msgstr "下列分支没有完全被åˆå¹¶åˆ° %s:" -#: lib/branch_delete.tcl:115 -msgid "" -"Recovering deleted branches is difficult. \n" -"\n" -" Delete the selected branches?" -msgstr "" -"æ¢å¤è¢«åˆ 除的分支éžå¸¸å›°éš¾.\n" -"\n" -"是å¦è¦åˆ 除所选分支?" - #: lib/branch_delete.tcl:141 #, tcl-format msgid "" diff --git a/git-gui/windows/git-gui.sh b/git-gui/windows/git-gui.sh index 53c3a94686..66bbb2f8fa 100644 --- a/git-gui/windows/git-gui.sh +++ b/git-gui/windows/git-gui.sh @@ -3,7 +3,12 @@ exec wish "$0" -- "$@" if { $argc >=2 && [lindex $argv 0] == "--working-dir" } { - cd [lindex $argv 1] + set workdir [lindex $argv 1] + cd $workdir + if {[lindex [file split $workdir] end] eq {.git}} { + # Workaround for Explorer right click "Git GUI Here" on .git/ + cd .. + } set argv [lrange $argv 2 end] incr argc -2 } diff --git a/git-instaweb.sh b/git-instaweb.sh index 0843372b57..5f4419b69b 100755 --- a/git-instaweb.sh +++ b/git-instaweb.sh @@ -49,7 +49,7 @@ resolve_full_httpd () { esac httpd_only="$(echo $httpd | cut -f1 -d' ')" - if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null;; esac + if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null 2>&1;; esac then full_httpd=$httpd else @@ -179,11 +179,74 @@ lighttpd_conf () { cat > "$conf" <<EOF server.document-root = "$fqgitdir/gitweb" server.port = $port -server.modules = ( "mod_cgi" ) +server.modules = ( "mod_setenv", "mod_cgi" ) server.indexfiles = ( "gitweb.cgi" ) server.pid-file = "$fqgitdir/pid" +server.errorlog = "$fqgitdir/gitweb/error.log" + +# to enable, add "mod_access", "mod_accesslog" to server.modules +# variable above and uncomment this +#accesslog.filename = "$fqgitdir/gitweb/access.log" + +setenv.add-environment = ( "PATH" => "/usr/local/bin:/usr/bin:/bin" ) + cgi.assign = ( ".cgi" => "" ) -mimetype.assign = ( ".css" => "text/css" ) + +# mimetype mapping +mimetype.assign = ( + ".pdf" => "application/pdf", + ".sig" => "application/pgp-signature", + ".spl" => "application/futuresplash", + ".class" => "application/octet-stream", + ".ps" => "application/postscript", + ".torrent" => "application/x-bittorrent", + ".dvi" => "application/x-dvi", + ".gz" => "application/x-gzip", + ".pac" => "application/x-ns-proxy-autoconfig", + ".swf" => "application/x-shockwave-flash", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".tar" => "application/x-tar", + ".zip" => "application/zip", + ".mp3" => "audio/mpeg", + ".m3u" => "audio/x-mpegurl", + ".wma" => "audio/x-ms-wma", + ".wax" => "audio/x-ms-wax", + ".ogg" => "application/ogg", + ".wav" => "audio/x-wav", + ".gif" => "image/gif", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".xbm" => "image/x-xbitmap", + ".xpm" => "image/x-xpixmap", + ".xwd" => "image/x-xwindowdump", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".js" => "text/javascript", + ".asc" => "text/plain", + ".c" => "text/plain", + ".cpp" => "text/plain", + ".log" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mov" => "video/quicktime", + ".qt" => "video/quicktime", + ".avi" => "video/x-msvideo", + ".asf" => "video/x-ms-asf", + ".asx" => "video/x-ms-asf", + ".wmv" => "video/x-ms-wmv", + ".bz2" => "application/x-bzip", + ".tbz" => "application/x-bzip-compressed-tar", + ".tar.bz2" => "application/x-bzip-compressed-tar", + "" => "text/plain" + ) EOF test x"$local" = xtrue && echo 'server.bind = "127.0.0.1"' >> "$conf" } diff --git a/git-merge-one-file.sh b/git-merge-one-file.sh index e1eb963266..9c2c1b7202 100755 --- a/git-merge-one-file.sh +++ b/git-merge-one-file.sh @@ -113,6 +113,10 @@ case "${1:-.}${2:-.}${3:-.}" in src1=`git-unpack-file $2` git merge-file "$src1" "$orig" "$src2" ret=$? + msg= + if [ $ret -ne 0 ]; then + msg='content conflict' + fi # Create the working tree file, using "our tree" version from the # index, and then store the result of the merge. @@ -120,7 +124,10 @@ case "${1:-.}${2:-.}${3:-.}" in rm -f -- "$orig" "$src1" "$src2" if [ "$6" != "$7" ]; then - echo "ERROR: Permissions conflict: $5->$6,$7." + if [ -n "$msg" ]; then + msg="$msg, " + fi + msg="${msg}permissions conflict: $5->$6,$7" ret=1 fi if [ "$1" = '' ]; then @@ -128,7 +135,7 @@ case "${1:-.}${2:-.}${3:-.}" in fi if [ $ret -ne 0 ]; then - echo "ERROR: Merge conflict in $4" + echo "ERROR: $msg in $4" exit 1 fi exec git update-index -- "$4" diff --git a/git-merge-resolve.sh b/git-merge-resolve.sh index 93bcfc2f5d..c9da747fcf 100755 --- a/git-merge-resolve.sh +++ b/git-merge-resolve.sh @@ -37,10 +37,10 @@ then exit 2 fi -git update-index --refresh 2>/dev/null +git update-index -q --refresh git read-tree -u -m --aggressive $bases $head $remotes || exit 2 echo "Trying simple merge." -if result_tree=$(git write-tree 2>/dev/null) +if result_tree=$(git write-tree 2>/dev/null) then exit 0 else diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh new file mode 100644 index 0000000000..bfb01f7842 --- /dev/null +++ b/git-mergetool--lib.sh @@ -0,0 +1,406 @@ +# git-mergetool--lib is a library for common merge tool functions +diff_mode() { + test "$TOOL_MODE" = diff +} + +merge_mode() { + test "$TOOL_MODE" = merge +} + +translate_merge_tool_path () { + case "$1" in + vimdiff) + echo vim + ;; + gvimdiff) + echo gvim + ;; + emerge) + echo emacs + ;; + araxis) + echo compare + ;; + *) + echo "$1" + ;; + esac +} + +check_unchanged () { + if test "$MERGED" -nt "$BACKUP"; then + status=0 + else + while true; do + echo "$MERGED seems unchanged." + printf "Was the merge successful? [y/n] " + read answer < /dev/tty + case "$answer" in + y*|Y*) status=0; break ;; + n*|N*) status=1; break ;; + esac + done + fi +} + +valid_tool () { + case "$1" in + kdiff3 | tkdiff | xxdiff | meld | opendiff | \ + emerge | vimdiff | gvimdiff | ecmerge | diffuse | araxis) + ;; # happy + tortoisemerge) + if ! merge_mode; then + return 1 + fi + ;; + kompare) + if ! diff_mode; then + return 1 + fi + ;; + *) + if test -z "$(get_merge_tool_cmd "$1")"; then + return 1 + fi + ;; + esac +} + +get_merge_tool_cmd () { + # Prints the custom command for a merge tool + if test -n "$1"; then + merge_tool="$1" + else + merge_tool="$(get_merge_tool)" + fi + if diff_mode; then + echo "$(git config difftool.$merge_tool.cmd || + git config mergetool.$merge_tool.cmd)" + else + echo "$(git config mergetool.$merge_tool.cmd)" + fi +} + +run_merge_tool () { + merge_tool_path="$(get_merge_tool_path "$1")" || exit + base_present="$2" + status=0 + + case "$1" in + kdiff3) + if merge_mode; then + if $base_present; then + ("$merge_tool_path" --auto \ + --L1 "$MERGED (Base)" \ + --L2 "$MERGED (Local)" \ + --L3 "$MERGED (Remote)" \ + -o "$MERGED" \ + "$BASE" "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1) + else + ("$merge_tool_path" --auto \ + --L1 "$MERGED (Local)" \ + --L2 "$MERGED (Remote)" \ + -o "$MERGED" \ + "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1) + fi + status=$? + else + ("$merge_tool_path" --auto \ + --L1 "$MERGED (A)" \ + --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \ + > /dev/null 2>&1) + fi + ;; + kompare) + "$merge_tool_path" "$LOCAL" "$REMOTE" + ;; + tkdiff) + if merge_mode; then + if $base_present; then + "$merge_tool_path" -a "$BASE" \ + -o "$MERGED" "$LOCAL" "$REMOTE" + else + "$merge_tool_path" \ + -o "$MERGED" "$LOCAL" "$REMOTE" + fi + status=$? + else + "$merge_tool_path" "$LOCAL" "$REMOTE" + fi + ;; + meld) + if merge_mode; then + touch "$BACKUP" + "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE" + check_unchanged + else + "$merge_tool_path" "$LOCAL" "$REMOTE" + fi + ;; + diffuse) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" \ + "$LOCAL" "$MERGED" "$REMOTE" \ + "$BASE" | cat + else + "$merge_tool_path" \ + "$LOCAL" "$MERGED" "$REMOTE" | cat + fi + check_unchanged + else + "$merge_tool_path" "$LOCAL" "$REMOTE" | cat + fi + ;; + vimdiff) + if merge_mode; then + touch "$BACKUP" + "$merge_tool_path" -d -c "wincmd l" \ + "$LOCAL" "$MERGED" "$REMOTE" + check_unchanged + else + "$merge_tool_path" -d -c "wincmd l" \ + "$LOCAL" "$REMOTE" + fi + ;; + gvimdiff) + if merge_mode; then + touch "$BACKUP" + "$merge_tool_path" -d -c "wincmd l" -f \ + "$LOCAL" "$MERGED" "$REMOTE" + check_unchanged + else + "$merge_tool_path" -d -c "wincmd l" -f \ + "$LOCAL" "$REMOTE" + fi + ;; + xxdiff) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" -X --show-merged-pane \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$MERGED" \ + "$LOCAL" "$BASE" "$REMOTE" + else + "$merge_tool_path" -X $extra \ + -R 'Accel.SaveAsMerged: "Ctrl-S"' \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + --merged-file "$MERGED" \ + "$LOCAL" "$REMOTE" + fi + check_unchanged + else + "$merge_tool_path" \ + -R 'Accel.Search: "Ctrl+F"' \ + -R 'Accel.SearchForward: "Ctrl-G"' \ + "$LOCAL" "$REMOTE" + fi + ;; + opendiff) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + -ancestor "$BASE" \ + -merge "$MERGED" | cat + else + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + -merge "$MERGED" | cat + fi + check_unchanged + else + "$merge_tool_path" "$LOCAL" "$REMOTE" | cat + fi + ;; + ecmerge) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \ + --default --mode=merge3 --to="$MERGED" + else + "$merge_tool_path" "$LOCAL" "$REMOTE" \ + --default --mode=merge2 --to="$MERGED" + fi + check_unchanged + else + "$merge_tool_path" --default --mode=diff2 \ + "$LOCAL" "$REMOTE" + fi + ;; + emerge) + if merge_mode; then + if $base_present; then + "$merge_tool_path" \ + -f emerge-files-with-ancestor-command \ + "$LOCAL" "$REMOTE" "$BASE" \ + "$(basename "$MERGED")" + else + "$merge_tool_path" \ + -f emerge-files-command \ + "$LOCAL" "$REMOTE" \ + "$(basename "$MERGED")" + fi + status=$? + else + "$merge_tool_path" -f emerge-files-command \ + "$LOCAL" "$REMOTE" + fi + ;; + tortoisemerge) + if $base_present; then + touch "$BACKUP" + "$merge_tool_path" \ + -base:"$BASE" -mine:"$LOCAL" \ + -theirs:"$REMOTE" -merged:"$MERGED" + check_unchanged + else + echo "TortoiseMerge cannot be used without a base" 1>&2 + status=1 + fi + ;; + araxis) + if merge_mode; then + touch "$BACKUP" + if $base_present; then + "$merge_tool_path" -wait -merge -3 -a1 \ + "$BASE" "$LOCAL" "$REMOTE" "$MERGED" \ + >/dev/null 2>&1 + else + "$merge_tool_path" -wait -2 \ + "$LOCAL" "$REMOTE" "$MERGED" \ + >/dev/null 2>&1 + fi + check_unchanged + else + "$merge_tool_path" -wait -2 "$LOCAL" "$REMOTE" \ + >/dev/null 2>&1 + fi + ;; + *) + merge_tool_cmd="$(get_merge_tool_cmd "$1")" + if test -z "$merge_tool_cmd"; then + if merge_mode; then + status=1 + fi + break + fi + if merge_mode; then + trust_exit_code="$(git config --bool \ + mergetool."$1".trustExitCode || echo false)" + if test "$trust_exit_code" = "false"; then + touch "$BACKUP" + ( eval $merge_tool_cmd ) + check_unchanged + else + ( eval $merge_tool_cmd ) + status=$? + fi + else + ( eval $merge_tool_cmd ) + fi + ;; + esac + return $status +} + +guess_merge_tool () { + if merge_mode; then + tools="tortoisemerge" + else + tools="kompare" + fi + if test -n "$DISPLAY"; then + if test -n "$GNOME_DESKTOP_SESSION_ID" ; then + tools="meld opendiff kdiff3 tkdiff xxdiff $tools" + else + tools="opendiff kdiff3 tkdiff xxdiff meld $tools" + fi + tools="$tools gvimdiff diffuse ecmerge araxis" + fi + if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then + # $EDITOR is emacs so add emerge as a candidate + tools="$tools emerge vimdiff" + elif echo "${VISUAL:-$EDITOR}" | grep vim > /dev/null 2>&1; then + # $EDITOR is vim so add vimdiff as a candidate + tools="$tools vimdiff emerge" + else + tools="$tools emerge vimdiff" + fi + echo >&2 "merge tool candidates: $tools" + + # Loop over each candidate and stop when a valid merge tool is found. + for i in $tools + do + merge_tool_path="$(translate_merge_tool_path "$i")" + if type "$merge_tool_path" > /dev/null 2>&1; then + echo "$i" + return 0 + fi + done + + echo >&2 "No known merge resolution program available." + return 1 +} + +get_configured_merge_tool () { + # Diff mode first tries diff.tool and falls back to merge.tool. + # Merge mode only checks merge.tool + if diff_mode; then + merge_tool=$(git config diff.tool || git config merge.tool) + else + merge_tool=$(git config merge.tool) + fi + if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then + echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool" + echo >&2 "Resetting to default..." + return 1 + fi + echo "$merge_tool" +} + +get_merge_tool_path () { + # A merge tool has been set, so verify that it's valid. + if test -n "$1"; then + merge_tool="$1" + else + merge_tool="$(get_merge_tool)" + fi + if ! valid_tool "$merge_tool"; then + echo >&2 "Unknown merge tool $merge_tool" + exit 1 + fi + if diff_mode; then + merge_tool_path=$(git config difftool."$merge_tool".path || + git config mergetool."$merge_tool".path) + else + merge_tool_path=$(git config mergetool."$merge_tool".path) + fi + if test -z "$merge_tool_path"; then + merge_tool_path="$(translate_merge_tool_path "$merge_tool")" + fi + if test -z "$(get_merge_tool_cmd "$merge_tool")" && + ! type "$merge_tool_path" > /dev/null 2>&1; then + echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\ + "'$merge_tool_path'" + exit 1 + fi + echo "$merge_tool_path" +} + +get_merge_tool () { + # Check if a merge tool has been configured + merge_tool=$(get_configured_merge_tool) + # Try to guess an appropriate merge tool if no tool has been set. + if test -z "$merge_tool"; then + merge_tool="$(guess_merge_tool)" || exit + fi + echo "$merge_tool" +} diff --git a/git-mergetool.sh b/git-mergetool.sh index 87fa88af55..b52a7410bc 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -11,7 +11,9 @@ USAGE='[--tool=tool] [-y|--no-prompt|--prompt] [file to merge] ...' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= +TOOL_MODE=merge . git-sh-setup +. git-mergetool--lib require_work_tree # Returns true if the mode reflects a symlink @@ -110,22 +112,6 @@ resolve_deleted_merge () { done } -check_unchanged () { - if test "$MERGED" -nt "$BACKUP" ; then - status=0; - else - while true; do - echo "$MERGED seems unchanged." - printf "Was the merge successful? [y/n] " - read answer < /dev/tty - case "$answer" in - y*|Y*) status=0; break ;; - n*|N*) status=1; break ;; - esac - done - fi -} - checkout_staged_file () { tmpfile=$(expr "$(git checkout-index --temp --stage="$1" "$2")" : '\([^ ]*\) ') @@ -137,7 +123,7 @@ checkout_staged_file () { merge_file () { MERGED="$1" - f=`git ls-files -u -- "$MERGED"` + f=$(git ls-files -u -- "$MERGED") if test -z "$f" ; then if test ! -f "$MERGED" ; then echo "$MERGED: file not found" @@ -156,9 +142,9 @@ merge_file () { mv -- "$MERGED" "$BACKUP" cp -- "$BACKUP" "$MERGED" - base_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}'` - 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_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}') + 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 && checkout_staged_file 1 "$MERGED" "$BASE" local_present && checkout_staged_file 2 "$MERGED" "$LOCAL" @@ -188,97 +174,13 @@ merge_file () { read ans fi - case "$merge_tool" in - kdiff3) - if base_present ; then - ("$merge_tool_path" --auto --L1 "$MERGED (Base)" --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" \ - -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1) - else - ("$merge_tool_path" --auto --L1 "$MERGED (Local)" --L2 "$MERGED (Remote)" \ - -o "$MERGED" "$LOCAL" "$REMOTE" > /dev/null 2>&1) - fi - status=$? - ;; - tkdiff) - if base_present ; then - "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE" - else - "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE" - fi - status=$? - ;; - 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" -c "wincmd l" -f "$LOCAL" "$MERGED" "$REMOTE" - check_unchanged - ;; - xxdiff) - touch "$BACKUP" - if base_present ; then - "$merge_tool_path" -X --show-merged-pane \ - -R 'Accel.SaveAsMerged: "Ctrl-S"' \ - -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ - --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE" - else - "$merge_tool_path" -X --show-merged-pane \ - -R 'Accel.SaveAsMerged: "Ctrl-S"' \ - -R 'Accel.Search: "Ctrl+F"' \ - -R 'Accel.SearchForward: "Ctrl-G"' \ - --merged-file "$MERGED" "$LOCAL" "$REMOTE" - fi - check_unchanged - ;; - opendiff) - touch "$BACKUP" - if base_present; then - "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED" | cat - else - "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED" | cat - fi - check_unchanged - ;; - ecmerge) - touch "$BACKUP" - if base_present; then - "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED" - else - "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED" - fi - check_unchanged - ;; - emerge) - if base_present ; then - "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$MERGED")" - else - "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$MERGED")" - fi - status=$? - ;; - *) - if test -n "$merge_tool_cmd"; then - if test "$merge_tool_trust_exit_code" = "false"; then - touch "$BACKUP" - ( eval $merge_tool_cmd ) - check_unchanged - else - ( eval $merge_tool_cmd ) - status=$? - fi - fi - ;; - esac - if test "$status" -ne 0; then + if base_present; then + present=true + else + present=false + fi + + if ! run_merge_tool "$merge_tool" "$present"; then echo "merge of $MERGED failed" 1>&2 mv -- "$BACKUP" "$MERGED" @@ -308,7 +210,7 @@ do -t|--tool*) case "$#,$1" in *,*=*) - merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'` + merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;; 1,*) usage ;; @@ -337,38 +239,6 @@ do shift done -valid_custom_tool() -{ - merge_tool_cmd="$(git config mergetool.$1.cmd)" - test -n "$merge_tool_cmd" -} - -valid_tool() { - case "$1" in - kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge) - ;; # happy - *) - if ! valid_custom_tool "$1"; then - return 1 - fi - ;; - esac -} - -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 -} - prompt_after_failed_merge() { while true; do printf "Continue merging other unresolved paths (y/n) ? " @@ -387,67 +257,16 @@ prompt_after_failed_merge() { } 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 - -if test -z "$merge_tool" ; then - if test -n "$DISPLAY"; then - if test -n "$GNOME_DESKTOP_SESSION_ID" ; then - 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 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 - echo "merge tool candidates: $merge_tool_candidates" - 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 - if ! valid_tool "$merge_tool"; then - echo >&2 "Unknown merge_tool $merge_tool" - exit 1 - fi - - 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'" - exit 1 - fi - - if ! test -z "$merge_tool_cmd"; then - merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)" - fi + merge_tool=$(get_merge_tool "$merge_tool") || exit fi +merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)" +merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)" last_status=0 rollup_status=0 if test $# -eq 0 ; then - files=`git ls-files -u | sed -e 's/^[^ ]* //' | sort -u` + files=$(git ls-files -u | sed -e 's/^[^ ]* //' | sort -u) if test -z "$files" ; then echo "No files need merging" exit 0 diff --git a/git-parse-remote.sh b/git-parse-remote.sh index 695a4094bb..a296719861 100755 --- a/git-parse-remote.sh +++ b/git-parse-remote.sh @@ -2,7 +2,7 @@ # git-ls-remote could be called from outside a git managed repository; # this would fail in that case and would issue an error message. -GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || :; +GIT_DIR=$(git rev-parse -q --git-dir) || :; get_data_source () { case "$1" in diff --git a/git-pull.sh b/git-pull.sh index c892059605..cab367ada0 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -16,7 +16,7 @@ cd_to_toplevel test -z "$(git ls-files -u)" || die "You are in the middle of a conflicted merge." -strategy_args= no_stat= no_commit= squash= no_ff= log_arg= verbosity= +strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity= curr_branch=$(git symbolic-ref -q HEAD) curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||") rebase=$(git config --bool branch.$curr_branch_short.rebase) @@ -28,9 +28,9 @@ do -v|--verbose) verbosity="$verbosity -v" ;; -n|--no-stat|--no-summary) - no_stat=-n ;; + diffstat=--no-stat ;; --stat|--summary) - no_stat=$1 ;; + diffstat=--stat ;; --log|--no-log) log_arg=$1 ;; --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit) @@ -147,7 +147,7 @@ then echo >&2 "Warning: fetch updated the current branch head." echo >&2 "Warning: fast forwarding your working tree from" echo >&2 "Warning: commit $orig_head." - git update-index --refresh 2>/dev/null + git update-index -q --refresh git read-tree -u -m "$orig_head" "$curr_head" || die 'Cannot fast-forward your working tree. After making sure that you saved anything precious from @@ -176,13 +176,11 @@ case "$merge_head" in ?*' '?*) if test -z "$orig_head" then - echo >&2 "Cannot merge multiple branches into empty head" - exit 1 + die "Cannot merge multiple branches into empty head" fi if test true = "$rebase" then - echo >&2 "Cannot rebase onto multiple branches" - exit 1 + die "Cannot rebase onto multiple branches" fi ;; esac @@ -196,7 +194,7 @@ fi merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit test true = "$rebase" && - exec git-rebase $strategy_args --onto $merge_head \ + exec git-rebase $diffstat $strategy_args --onto $merge_head \ ${oldremoteref:-$merge_head} -exec git-merge $no_stat $no_commit $squash $no_ff $log_arg $strategy_args \ +exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \ "$merge_name" HEAD $merge_head $verbosity diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh index 3dc659dd58..f96d887d23 100755 --- a/git-rebase--interactive.sh +++ b/git-rebase--interactive.sh @@ -420,7 +420,7 @@ do_next () { NEWHEAD=$(git rev-parse HEAD) && case $HEADNAME in refs/*) - message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" && + message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO" && git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD && git symbolic-ref HEAD $HEADNAME ;; @@ -442,6 +442,30 @@ do_rest () { done } +# skip picking commits whose parents are unchanged +skip_unnecessary_picks () { + fd=3 + while read command sha1 rest + do + # fd=3 means we skip the command + case "$fd,$command,$(git rev-parse --verify --quiet $sha1^)" in + 3,pick,"$ONTO"*|3,p,"$ONTO"*) + # pick a commit whose parent is current $ONTO -> skip + ONTO=$sha1 + ;; + 3,#*|3,,*) + # copy comments + ;; + *) + fd=1 + ;; + esac + echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd + done <"$TODO" >"$TODO.new" 3>>"$DONE" && + mv -f "$TODO".new "$TODO" || + die "Could not skip unnecessary pick commands" +} + # check if no other options are set is_standalone () { test $# -eq 2 -a "$2" = '--' && @@ -746,6 +770,8 @@ EOF has_action "$TODO" || die_abort "Nothing to do" + test -d "$REWRITTEN" || skip_unnecessary_picks + git update-ref ORIG_HEAD $HEAD output git checkout $ONTO && do_rest ;; diff --git a/git-rebase.sh b/git-rebase.sh index 368c0ef434..334629fc97 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>|--root] [<branch>]' +USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--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> @@ -46,8 +46,10 @@ do_merge= dotest="$GIT_DIR"/rebase-merge prec=4 verbose= +diffstat=$(git config --bool rebase.stat) git_am_opt= rebase_root= +force_rebase= continue_merge () { test -n "$prev_head" || die "prev_head must be defined" @@ -166,10 +168,8 @@ run_pre_rebase_hook () { if test -z "$OK_TO_SKIP_PRE_REBASE" && test -x "$GIT_DIR/hooks/pre-rebase" then - "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || { - echo >&2 "The pre-rebase hook refused to rebase." - exit 1 - } + "$GIT_DIR/hooks/pre-rebase" ${1+"$@"} || + die "The pre-rebase hook refused to rebase." fi } @@ -289,11 +289,27 @@ do esac do_merge=t ;; + -n|--no-stat) + diffstat= + ;; + --stat) + diffstat=t + ;; -v|--verbose) verbose=t + diffstat=t ;; --whitespace=*) git_am_opt="$git_am_opt $1" + case "$1" in + --whitespace=fix|--whitespace=strip) + force_rebase=t + ;; + esac + ;; + --committer-date-is-author-date|--ignore-date) + git_am_opt="$git_am_opt $1" + force_rebase=t ;; -C*) git_am_opt="$git_am_opt $1" @@ -301,6 +317,9 @@ do --root) rebase_root=t ;; + -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase) + force_rebase=t + ;; -*) usage ;; @@ -338,8 +357,7 @@ fi # The tree must be really really clean. if ! git update-index --ignore-submodules --refresh; then - echo >&2 "cannot rebase: you have unstaged changes" - exit 1 + die "cannot rebase: you have unstaged changes" fi diff=$(git diff-index --cached --name-status -r --ignore-submodules HEAD --) case "$diff" in @@ -420,17 +438,15 @@ if test "$upstream" = "$onto" && test "$mb" = "$onto" && # linear history? ! (git rev-list --parents "$onto".."$branch" | grep " .* ") > /dev/null then - # Lazily switch to the target branch if needed... - test -z "$switch_to" || git checkout "$switch_to" - echo >&2 "Current branch $branch_name is up to date." - exit 0 -fi - -if test -n "$verbose" -then - echo "Changes from $mb to $onto:" - # We want color (if set), but no pager - GIT_PAGER='' git diff --stat --summary "$mb" "$onto" + if test -z "$force_rebase" + then + # Lazily switch to the target branch if needed... + test -z "$switch_to" || git checkout "$switch_to" + echo >&2 "Current branch $branch_name is up to date." + exit 0 + else + echo "Current branch $branch_name is up to date, rebase forced." + fi fi # Detach HEAD and reset the tree @@ -438,6 +454,16 @@ echo "First, rewinding head to replay your work on top of it..." git checkout -q "$onto^0" || die "could not detach HEAD" git update-ref ORIG_HEAD $branch +if test -n "$diffstat" +then + if test -n "$verbose" + then + echo "Changes from $mb to $onto:" + fi + # We want color (if set), but no pager + GIT_PAGER='' git diff --stat --summary "$mb" "$onto" +fi + # If the $onto is a proper descendant of the tip of the branch, then # we just fast forwarded. if test "$mb" = "$branch" diff --git a/git-send-email.perl b/git-send-email.perl index 09fe3d95c4..303e03a8ce 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -75,6 +75,8 @@ git send-email [options] <file | directory | rev-list options > --[no-]thread * Use In-Reply-To: field. Default on. Administering: + --confirm <str> * Confirm recipients before sending; + auto, cc, compose, always, or never. --quiet * Output one line of info per email. --dry-run * Don't actually send the emails. --[no-]validate * Perform patch sanity checks. Default on. @@ -181,7 +183,7 @@ sub do_edit { my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd); my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption); my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts); -my ($validate); +my ($validate, $confirm); my (@suppress_cc); my %config_bool_settings = ( @@ -207,6 +209,8 @@ my %config_settings = ( "suppresscc" => \@suppress_cc, "envelopesender" => \$envelope_sender, "multiedit" => \$multiedit, + "confirm" => \$confirm, + "from" => \$sender, ); # Handle Uncouth Termination @@ -258,6 +262,7 @@ my $rc = GetOptions("sender|from=s" => \$sender, "suppress-from!" => \$suppress_from, "suppress-cc=s" => \@suppress_cc, "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc, + "confirm=s" => \$confirm, "dry-run" => \$dry_run, "envelope-sender=s" => \$envelope_sender, "thread!" => \$thread, @@ -346,6 +351,14 @@ if ($suppress_cc{'body'}) { delete $suppress_cc{'body'}; } +# Set confirm's default value +my $confirm_unconfigured = !defined $confirm; +if ($confirm_unconfigured) { + $confirm = scalar %suppress_cc ? 'compose' : 'auto'; +}; +die "Unknown --confirm setting: '$confirm'\n" + unless $confirm =~ /^(?:auto|cc|compose|always|never)/; + # Debugging, print out the suppressions. if (0) { print "suppressions:\n"; @@ -397,7 +410,7 @@ my %parse_alias = ( mailrc => sub { my $fh = shift; while (<$fh>) { if (/^alias\s+(\S+)\s+(.*)$/) { # spaces delimit multiple addresses - $aliases{$1} = [ split(/\s+/, $2) ]; + $aliases{$1} = [ quotewords('\s+', 0, $2) ]; }}}, pine => sub { my $fh = shift; my $f='\t[^\t]*'; for (my $x = ''; defined($x); $x = $_) { @@ -406,6 +419,14 @@ my %parse_alias = ( $x =~ /^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ or next; $aliases{$1} = [ split_addrs($2) ]; }}, + elm => sub { my $fh = shift; + while (<$fh>) { + if (/^(\S+)\s+=\s+[^=]+=\s(\S+)/) { + my ($alias, $addr) = ($1, $2); + $aliases{$alias} = [ split_addrs($addr) ]; + } + } }, + gnus => sub { my $fh = shift; while (<$fh>) { if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) { $aliases{$1} = [ $2 ]; @@ -517,7 +538,7 @@ if ($compose) { print C <<EOT; From $tpl_sender # This line is ignored. -GIT: Lines beginning in "GIT: " will be removed. +GIT: Lines beginning in "GIT:" will be removed. GIT: Consider including an overall diffstat or table of contents GIT: for the patch you are writing. GIT: @@ -532,8 +553,6 @@ EOT } close(C); - my $editor = $ENV{GIT_EDITOR} || Git::config(@repo, "core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi"; - if ($annotate) { do_edit($compose_filename, @files); } else { @@ -550,7 +569,7 @@ EOT my $in_body = 0; my $summary_empty = 1; while(<C>) { - next if m/^GIT: /; + next if m/^GIT:/; if ($in_body) { $summary_empty = 0 unless (/^\n$/); } elsif (/^\n$/) { @@ -558,7 +577,7 @@ EOT if ($need_8bit_cte) { print C2 "MIME-Version: 1.0\n", "Content-Type: text/plain; ", - "charset=utf-8\n", + "charset=UTF-8\n", "Content-Transfer-Encoding: 8bit\n"; } } elsif (/^MIME-Version:/i) { @@ -594,32 +613,43 @@ EOT do_edit(@files); } +sub ask { + my ($prompt, %arg) = @_; + my $valid_re = $arg{valid_re}; + my $default = $arg{default}; + my $resp; + my $i = 0; + return defined $default ? $default : undef + unless defined $term->IN and defined fileno($term->IN) and + defined $term->OUT and defined fileno($term->OUT); + while ($i++ < 10) { + $resp = $term->readline($prompt); + if (!defined $resp) { # EOF + print "\n"; + return defined $default ? $default : undef; + } + if ($resp eq '' and defined $default) { + return $default; + } + if (!defined $valid_re or $resp =~ /$valid_re/) { + return $resp; + } + } + return undef; +} + my $prompting = 0; if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; - - while (1) { - $_ = $term->readline("Who should the emails appear to be from? [$sender] "); - last if defined $_; - print "\n"; - } - - $sender = $_ if ($_); + $sender = ask("Who should the emails appear to be from? [$sender] ", + default => $sender); print "Emails will be sent from: ", $sender, "\n"; $prompting++; } if (!@to) { - - - while (1) { - $_ = $term->readline("Who should the emails be sent to? ", ""); - last if defined $_; - print "\n"; - } - - my $to = $_; - push @to, parse_address_line($to); + my $to = ask("Who should the emails be sent to? "); + push @to, parse_address_line($to) if defined $to; # sanitized/validated later $prompting++; } @@ -639,13 +669,8 @@ sub expand_aliases { @bcclist = expand_aliases(@bcclist); if ($thread && !defined $initial_reply_to && $prompting) { - while (1) { - $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to); - last if defined $_; - print "\n"; - } - - $initial_reply_to = $_; + $initial_reply_to = ask( + "Message-ID to be used as In-Reply-To for the first email? "); } if (defined $initial_reply_to) { $initial_reply_to =~ s/^\s*<?//; @@ -663,25 +688,13 @@ if (!defined $smtp_server) { $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug* } -if ($compose) { - while (1) { - $_ = $term->readline("Send this email? (y|n) "); - last if defined $_; - print "\n"; - } - - if (uc substr($_,0,1) ne 'Y') { - cleanup_compose_files(); - exit(0); - } - - if ($compose > 0) { - @files = ($compose_filename . ".final", @files); - } +if ($compose && $compose > 0) { + @files = ($compose_filename . ".final", @files); } # Variables we set as part of the loop over files -our ($message_id, %mail, $subject, $reply_to, $references, $message); +our ($message_id, %mail, $subject, $reply_to, $references, $message, + $needs_confirm, $message_num, $ask_default); sub extract_valid_address { my $address = shift; @@ -753,12 +766,20 @@ sub unquote_rfc2047 { sub quote_rfc2047 { local $_ = shift; - my $encoding = shift || 'utf-8'; + my $encoding = shift || 'UTF-8'; s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg; s/(.*)/=\?$encoding\?q\?$1\?=/; return $_; } +sub is_rfc2047_quoted { + my $s = shift; + my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+'; + my $encoded_text = '[!->@-~]+'; + length($s) <= 75 && + $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o; +} + # use the simplest quoting being able to handle the recipient sub sanitize_address { @@ -770,12 +791,13 @@ sub sanitize_address } # if recipient_name is already quoted, do nothing - if ($recipient_name =~ /^(".*"|=\?utf-8\?q\?.*\?=)$/) { + if (is_rfc2047_quoted($recipient_name)) { return $recipient; } # rfc2047 is needed if a non-ascii char is included if ($recipient_name =~ /[^[:ascii:]]/) { + $recipient_name =~ s/^"(.*)"$/$1/; $recipient_name = quote_rfc2047($recipient_name); } @@ -789,6 +811,10 @@ sub sanitize_address } +# Returns 1 if the message was sent, and 0 otherwise. +# In actuality, the whole program dies when there +# is an error sending a message. + sub send_message { my @recipients = unique_email_list(@to); @@ -837,6 +863,35 @@ X-Mailer: git-send-email $gitversion unshift (@sendmail_parameters, '-f', $raw_from) if(defined $envelope_sender); + if ($needs_confirm && !$dry_run) { + print "\n$header\n"; + if ($needs_confirm eq "inform") { + $confirm_unconfigured = 0; # squelch this message for the rest of this run + $ask_default = "y"; # assume yes on EOF since user hasn't explicitly asked for confirmation + print " The Cc list above has been expanded by additional\n"; + print " addresses found in the patch commit message. By default\n"; + print " send-email prompts before sending whenever this occurs.\n"; + print " This behavior is controlled by the sendemail.confirm\n"; + print " configuration setting.\n"; + print "\n"; + print " For additional information, run 'git send-email --help'.\n"; + print " To retain the current behavior, but squelch this message,\n"; + print " run 'git config --global sendemail.confirm auto'.\n\n"; + } + $_ = ask("Send this email? ([y]es|[n]o|[q]uit|[a]ll): ", + valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i, + default => $ask_default); + die "Send this email reply required" unless defined $_; + if (/^n/i) { + return 0; + } elsif (/^q/i) { + cleanup_compose_files(); + exit(0); + } elsif (/^a/i) { + $confirm = 'never'; + } + } + if ($dry_run) { # We don't want to send the email. } elsif ($smtp_server =~ m#^/#) { @@ -909,7 +964,7 @@ X-Mailer: git-send-email $gitversion $smtp->data or die $smtp->message; $smtp->datasend("$header\n$message") or die $smtp->message; $smtp->dataend() or die $smtp->message; - $smtp->ok or die "Failed to send $subject\n".$smtp->message; + $smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message; } if ($quiet) { printf (($dry_run ? "Dry-" : "")."Sent %s\n", $subject); @@ -930,11 +985,14 @@ X-Mailer: git-send-email $gitversion print "Result: OK\n"; } } + + return 1; } $reply_to = $initial_reply_to; $references = $initial_reply_to || ''; $subject = $initial_subject; +$message_num = 0; foreach my $t (@files) { open(F,"<",$t) or die "can't open file $t"; @@ -943,11 +1001,12 @@ foreach my $t (@files) { my $author_encoding; my $has_content_type; my $body_encoding; - @cc = @initial_cc; + @cc = (); @xh = (); my $input_format = undef; my @header = (); $message = ""; + $message_num++; # First unfold multiline header fields while(<F>) { last if /^\s*$/; @@ -1080,10 +1139,19 @@ foreach my $t (@files) { } } - send_message(); + $needs_confirm = ( + $confirm eq "always" or + ($confirm =~ /^(?:auto|cc)$/ && @cc) or + ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1)); + $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc); + + @cc = (@initial_cc, @cc); + + my $message_was_sent = send_message(); # set up for the next message - if ($chain_reply_to || !defined $reply_to || length($reply_to) == 0) { + if ($thread && $message_was_sent && + ($chain_reply_to || !defined $reply_to || length($reply_to) == 0)) { $reply_to = $message_id; if (length $references > 0) { $references .= "\n $message_id"; @@ -1094,13 +1162,10 @@ foreach my $t (@files) { $message_id = undef; } -if ($compose) { - cleanup_compose_files(); -} +cleanup_compose_files(); sub cleanup_compose_files() { - unlink($compose_filename, $compose_filename . ".final"); - + unlink($compose_filename, $compose_filename . ".final") if $compose; } $smtp->quit if $smtp; diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 838233926f..80acb7de72 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -11,6 +11,34 @@ # exporting it. unset CDPATH +git_broken_path_fix () { + case ":$PATH:" in + *:$1:*) : ok ;; + *) + PATH=$( + SANE_TOOL_PATH="$1" + IFS=: path= sep= + set x $PATH + shift + for elem + do + case "$SANE_TOOL_PATH:$elem" in + (?*:/bin | ?*:/usr/bin) + path="$path$sep$SANE_TOOL_PATH" + sep=: + SANE_TOOL_PATH= + esac + path="$path$sep$elem" + sep=: + done + echo "$path" + ) + ;; + esac +} + +# @@BROKEN_PATH_FIX@@ + die() { echo >&2 "$@" exit 1 diff --git a/git-stash.sh b/git-stash.sh index b9ace99704..e6a5867209 100755 --- a/git-stash.sh +++ b/git-stash.sh @@ -3,8 +3,8 @@ dashless=$(basename "$0" | sed -e 's/-/ /') USAGE="list [<options>] - or: $dashless (show | drop | pop ) [<stash>] - or: $dashless apply [--index] [<stash>] + or: $dashless ( show | drop ) [<stash>] + or: $dashless ( pop | apply ) [--index] [<stash>] or: $dashless branch <branchname> [<stash>] or: $dashless [save [--keep-index] [<message>]] or: $dashless clear" diff --git a/git-submodule.sh b/git-submodule.sh index 7c2e060ae7..19a3a840fd 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -15,8 +15,10 @@ require_work_tree command= branch= quiet= +reference= cached= nofetch= +update= # # print stuff on stdout unless -q was specified @@ -91,6 +93,7 @@ module_clone() { path=$1 url=$2 + reference="$3" # If there already is a directory at the submodule path, # expect it to be empty (since that is the default checkout @@ -106,7 +109,12 @@ module_clone() test -e "$path" && die "A file already exist at path '$path'" - git-clone -n "$url" "$path" || + if test -n "$reference" + then + git-clone "$reference" -n "$url" "$path" + else + git-clone -n "$url" "$path" + fi || die "Clone of '$url' into submodule path '$path' failed" } @@ -131,6 +139,15 @@ cmd_add() -q|--quiet) quiet=1 ;; + --reference) + case "$2" in '') usage ;; esac + reference="--reference=$2" + shift + ;; + --reference=*) + reference="$1" + shift + ;; --) shift break @@ -203,9 +220,16 @@ cmd_add() git config submodule."$path".url "$url" else - module_clone "$path" "$realrepo" || exit - (unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) || - die "Unable to checkout submodule '$path'" + module_clone "$path" "$realrepo" "$reference" || exit + ( + unset GIT_DIR + cd "$path" && + # ash fails to wordsplit ${branch:+-b "$branch"...} + case "$branch" in + '') git checkout -f -q ;; + ?*) git checkout -f -q -b "$branch" "origin/$branch" ;; + esac + ) || die "Unable to checkout submodule '$path'" fi git add "$path" || @@ -287,6 +311,11 @@ cmd_init() git config submodule."$name".url "$url" || die "Failed to register url for submodule path '$path'" + upd="$(git config -f .gitmodules submodule."$name".update)" + test -z "$upd" || + git config submodule."$name".update "$upd" || + die "Failed to register update mode for submodule path '$path'" + say "Submodule '$name' ($url) registered for path '$path'" done } @@ -307,13 +336,26 @@ cmd_update() quiet=1 ;; -i|--init) + init=1 shift - cmd_init "$@" || return ;; -N|--no-fetch) shift nofetch=1 ;; + -r|--rebase) + shift + update="rebase" + ;; + --reference) + case "$2" in '') usage ;; esac + reference="--reference=$2" + shift 2 + ;; + --reference=*) + reference="$1" + shift + ;; --) shift break @@ -327,11 +369,17 @@ cmd_update() esac done + if test -n "$init" + then + cmd_init "--" "$@" || return + fi + module_list "$@" | while read mode sha1 stage path do name=$(module_name "$path") || exit url=$(git config submodule."$name".url) + update_module=$(git config submodule."$name".update) if test -z "$url" then # Only mention uninitialized submodules when its @@ -344,7 +392,7 @@ cmd_update() if ! test -d "$path"/.git -o -f "$path"/.git then - module_clone "$path" "$url" || exit + module_clone "$path" "$url" "$reference"|| exit subsha1= else subsha1=$(unset GIT_DIR; cd "$path" && @@ -352,6 +400,11 @@ cmd_update() die "Unable to find current revision in submodule path '$path'" fi + if ! test -z "$update" + then + update_module=$update + fi + if test "$subsha1" != "$sha1" then force= @@ -367,11 +420,22 @@ cmd_update() 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'" + case "$update_module" in + rebase) + command="git rebase" + action="rebase" + msg="rebased onto" + ;; + *) + command="git checkout $force -q" + action="checkout" + msg="checked out" + ;; + esac - say "Submodule path '$path': checked out '$sha1'" + (unset GIT_DIR; cd "$path" && $command "$sha1") || + die "Unable to $action '$sha1' in submodule path '$path'" + say "Submodule path '$path': $msg '$sha1'" fi done } diff --git a/git-svn.perl b/git-svn.perl index 3b524207ff..33017974d0 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -5,7 +5,7 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION $sha1 $sha1_short $_revision $_repository - $_q $_authors %users/; + $_q $_authors $_authors_prog %users/; $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; $VERSION = '@@GIT_VERSION@@'; @@ -39,6 +39,7 @@ use Digest::MD5; use IO::File qw//; use File::Basename qw/dirname basename/; use File::Path qw/mkpath/; +use File::Spec; use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/; use IPC::Open3; use Git; @@ -47,7 +48,8 @@ BEGIN { # import functions from Git into our packages, en masse no strict 'refs'; foreach (qw/command command_oneline command_noisy command_output_pipe - command_input_pipe command_close_pipe/) { + command_input_pipe command_close_pipe + command_bidi_pipe command_close_bidi_pipe/) { for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher Git::SVN::Migration Git::SVN::Log Git::SVN), __PACKAGE__) { @@ -63,24 +65,26 @@ $sha1_short = qr/[a-f\d]{4,40}/; my ($_stdin, $_help, $_edit, $_message, $_file, $_template, $_shared, - $_version, $_fetch_all, $_no_rebase, + $_version, $_fetch_all, $_no_rebase, $_fetch_parent, $_merge, $_strategy, $_dry_run, $_local, $_prefix, $_no_checkout, $_url, $_verbose, $_git_format, $_commit_url, $_tag); $Git::SVN::_follow_parent = 1; +$_q ||= 0; 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, 'ignore-paths=s' => \$SVN::Git::Fetcher::_ignore_regex ); my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent, 'authors-file|A=s' => \$_authors, + 'authors-prog=s' => \$_authors_prog, 'repack:i' => \$Git::SVN::_repack, 'noMetadata' => \$Git::SVN::_no_metadata, 'useSvmProps' => \$Git::SVN::_use_svm_props, 'useSvnsyncProps' => \$Git::SVN::_use_svnsync_props, 'log-window-size=i' => \$Git::SVN::Ra::_log_window_size, 'no-checkout' => \$_no_checkout, - 'quiet|q' => \$_q, + 'quiet|q+' => \$_q, 'repack-flags|repack-args|repack-opts=s' => \$Git::SVN::_repack_flags, 'use-log-author' => \$Git::SVN::_use_log_author, @@ -111,6 +115,7 @@ my %cmd = ( fetch => [ \&cmd_fetch, "Download new revisions from SVN", { 'revision|r=s' => \$_revision, 'fetch-all|all' => \$_fetch_all, + 'parent|p' => \$_fetch_parent, %fc_opts } ], clone => [ \&cmd_clone, "Initialize and fetch revisions", { 'revision|r=s' => \$_revision, @@ -260,6 +265,9 @@ usage(0) if $_help; version() if $_version; usage(1) unless defined $cmd; load_authors() if $_authors; +if (defined $_authors_prog) { + $_authors_prog = "'" . File::Spec->rel2abs($_authors_prog) . "'"; +} unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) { Git::SVN::Migration::migration_check(); @@ -325,6 +333,7 @@ sub do_git_init_db { command_noisy(@init_db); $_repository = Git->repository(Repository => ".git"); } + command_noisy('config', 'core.autocrlf', 'false'); my $set; my $pfx = "svn-remote.$Git::SVN::default_repo_id"; foreach my $i (keys %icv) { @@ -333,6 +342,9 @@ sub do_git_init_db { command_noisy('config', "$pfx.$i", $icv{$i}); $set = $i; } + my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex; + command_noisy('config', "$pfx.ignore-paths", $$ignore_regex) + if defined $$ignore_regex; } sub init_subdir { @@ -354,6 +366,7 @@ sub cmd_clone { $path = basename($url) if !defined $path || !length $path; cmd_init($url, $path); Git::SVN::fetch_all($Git::SVN::default_repo_id); + command_oneline('config', 'svn.authorsfile', $_authors) if $_authors; } sub cmd_init { @@ -380,12 +393,21 @@ sub cmd_fetch { } my ($remote) = @_; if (@_ > 1) { - die "Usage: $0 fetch [--all] [svn-remote]\n"; + die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n"; } - $remote ||= $Git::SVN::default_repo_id; - if ($_fetch_all) { + if ($_fetch_parent) { + my ($url, $rev, $uuid, $gs) = working_head_info('HEAD'); + unless ($gs) { + die "Unable to determine upstream SVN information from ", + "working tree history\n"; + } + # just fetch, don't checkout. + $_no_checkout = 'true'; + $_fetch_all ? $gs->fetch_all : $gs->fetch; + } elsif ($_fetch_all) { cmd_multi_fetch(); } else { + $remote ||= $Git::SVN::default_repo_id; Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes()); } } @@ -1156,16 +1178,27 @@ sub get_commit_entry { } rename $commit_editmsg, $commit_msg or croak $!; { + require Encode; # SVN requires messages to be UTF-8 when entering the repo local $/; open $log_fh, '<', $commit_msg or croak $!; binmode $log_fh; chomp($log_entry{log} = <$log_fh>); - if (my $enc = Git::config('i18n.commitencoding')) { - require Encode; - Encode::from_to($log_entry{log}, $enc, 'UTF-8'); + my $enc = Git::config('i18n.commitencoding') || 'UTF-8'; + my $msg = $log_entry{log}; + + eval { $msg = Encode::decode($enc, $msg, 1) }; + if ($@) { + die "Could not decode as $enc:\n", $msg, + "\nPerhaps you need to set i18n.commitencoding\n"; } + + eval { $msg = Encode::encode('UTF-8', $msg, 1) }; + die "Could not encode as UTF-8:\n$msg\n" if $@; + + $log_entry{log} = $msg; + close $log_fh or croak $!; } unlink $commit_msg; @@ -1253,6 +1286,40 @@ sub cmt_metadata { command(qw/cat-file commit/, shift)))[-1]); } +sub cmt_sha2rev_batch { + my %s2r; + my ($pid, $in, $out, $ctx) = command_bidi_pipe(qw/cat-file --batch/); + my $list = shift; + + foreach my $sha (@{$list}) { + my $first = 1; + my $size = 0; + print $out $sha, "\n"; + + while (my $line = <$in>) { + if ($first && $line =~ /^[[:xdigit:]]{40}\smissing$/) { + last; + } elsif ($first && + $line =~ /^[[:xdigit:]]{40}\scommit\s(\d+)$/) { + $first = 0; + $size = $1; + next; + } elsif ($line =~ /^(git-svn-id: )/) { + my (undef, $rev, undef) = + extract_metadata($line); + $s2r{$sha} = $rev; + } + + $size -= length($line); + last if ($size == 0); + } + } + + command_close_bidi_pipe($pid, $in, $out, $ctx); + + return \%s2r; +} + sub working_head_info { my ($head, $refs) = @_; my @args = ('log', '--no-color', '--first-parent', '--pretty=medium'); @@ -2331,13 +2398,13 @@ sub do_git_commit { $self->{last_rev} = $log_entry->{revision}; $self->{last_commit} = $commit; - print "r$log_entry->{revision}"; + print "r$log_entry->{revision}" unless $::_q > 1; if (defined $log_entry->{svm_revision}) { - print " (\@$log_entry->{svm_revision})"; + print " (\@$log_entry->{svm_revision})" unless $::_q > 1; $self->rev_map_set($log_entry->{svm_revision}, $commit, 0, $self->svm_uuid); } - print " = $commit ($self->{ref_id})\n"; + print " = $commit ($self->{ref_id})\n" unless $::_q > 1; if (--$_gc_nr == 0) { $_gc_nr = $_gc_period; gc(); @@ -2351,7 +2418,10 @@ sub match_paths { if (my $path = $paths->{"/$self->{path}"}) { return ($path->{action} eq 'D') ? 0 : 1; } - $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//; + my $repos_root = $self->ra->{repos_root}; + my $extended_path = $self->{url} . '/' . $self->{path}; + $extended_path =~ s#^\Q$repos_root\E(/|$)##; + $self->{path_regex} ||= qr/^\/\Q$extended_path\E\//; if (grep /$self->{path_regex}/, keys %$paths) { return 1; } @@ -2610,12 +2680,33 @@ sub other_gs { $gs } +sub call_authors_prog { + my ($orig_author) = @_; + my $author = `$::_authors_prog $orig_author`; + if ($? != 0) { + die "$::_authors_prog failed with exit code $?\n" + } + if ($author =~ /^\s*(.+?)\s*<(.*)>\s*$/) { + my ($name, $email) = ($1, $2); + $email = undef if length $2 == 0; + return [$name, $email]; + } else { + die "Author: $orig_author: $::_authors_prog returned " + . "invalid author format: $author\n"; + } +} + sub check_author { my ($author) = @_; if (!defined $author || length $author == 0) { $author = '(no author)'; - } elsif (defined $::_authors && ! defined $::users{$author}) { - die "Author: $author not defined in $::_authors file\n"; + } + if (!defined $::users{$author}) { + if (defined $::_authors_prog) { + $::users{$author} = call_authors_prog($author); + } elsif (defined $::_authors) { + die "Author: $author not defined in $::_authors file\n"; + } } $author; } @@ -3282,6 +3373,8 @@ sub new { $self->{empty_symlinks} = _mark_empty_symlinks($git_svn, $switch_path); } + $self->{ignore_regex} = eval { command_oneline('config', '--get', + "svn-remote.$git_svn->{repo_id}.ignore-paths") }; $self->{empty} = {}; $self->{dir_prop} = {}; $self->{file_prop} = {}; @@ -3346,8 +3439,10 @@ sub in_dot_git { # return value: 0 -- don't ignore, 1 -- ignore sub is_path_ignored { - my ($path) = @_; + my ($self, $path) = @_; return 1 if in_dot_git($path); + return 1 if defined($self->{ignore_regex}) && + $path =~ m!$self->{ignore_regex}!; return 0 unless defined($_ignore_regex); return 1 if $path =~ m!$_ignore_regex!o; return 0; @@ -3378,7 +3473,7 @@ sub git_path { sub delete_entry { my ($self, $path, $rev, $pb) = @_; - return undef if is_path_ignored($path); + return undef if $self->is_path_ignored($path); my $gpath = $self->git_path($path); return undef if ($gpath eq ''); @@ -3411,7 +3506,7 @@ sub open_file { my ($self, $path, $pb, $rev) = @_; my ($mode, $blob); - goto out if is_path_ignored($path); + goto out if $self->is_path_ignored($path); my $gpath = $self->git_path($path); ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath") @@ -3431,7 +3526,7 @@ sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; my $mode; - if (!is_path_ignored($path)) { + if (!$self->is_path_ignored($path)) { my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); delete $self->{empty}->{$dir}; $mode = '100644'; @@ -3442,7 +3537,7 @@ sub add_file { sub add_directory { my ($self, $path, $cp_path, $cp_rev) = @_; - goto out if is_path_ignored($path); + goto out if $self->is_path_ignored($path); my $gpath = $self->git_path($path); if ($gpath eq '') { my ($ls, $ctx) = command_output_pipe(qw/ls-tree @@ -3466,7 +3561,7 @@ out: sub change_dir_prop { my ($self, $db, $prop, $value) = @_; - return undef if is_path_ignored($db->{path}); + return undef if $self->is_path_ignored($db->{path}); $self->{dir_prop}->{$db->{path}} ||= {}; $self->{dir_prop}->{$db->{path}}->{$prop} = $value; undef; @@ -3474,7 +3569,7 @@ sub change_dir_prop { sub absent_directory { my ($self, $path, $pb) = @_; - return undef if is_path_ignored($path); + return undef if $self->is_path_ignored($path); $self->{absent_dir}->{$pb->{path}} ||= []; push @{$self->{absent_dir}->{$pb->{path}}}, $path; undef; @@ -3482,7 +3577,7 @@ sub absent_directory { sub absent_file { my ($self, $path, $pb) = @_; - return undef if is_path_ignored($path); + return undef if $self->is_path_ignored($path); $self->{absent_file}->{$pb->{path}} ||= []; push @{$self->{absent_file}->{$pb->{path}}}, $path; undef; @@ -3490,7 +3585,7 @@ sub absent_file { sub change_file_prop { my ($self, $fb, $prop, $value) = @_; - return undef if is_path_ignored($fb->{path}); + return undef if $self->is_path_ignored($fb->{path}); if ($prop eq 'svn:executable') { if ($fb->{mode_b} != 120000) { $fb->{mode_b} = defined $value ? 100755 : 100644; @@ -3506,7 +3601,7 @@ sub change_file_prop { sub apply_textdelta { my ($self, $fb, $exp) = @_; - return undef if is_path_ignored($fb->{path}); + return undef if $self->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 @@ -3553,7 +3648,7 @@ sub apply_textdelta { sub close_file { my ($self, $fb, $exp) = @_; - return undef if is_path_ignored($fb->{path}); + return undef if $self->is_path_ignored($fb->{path}); my $hash; my $path = $self->git_path($fb->{path}); @@ -4381,6 +4476,7 @@ sub gs_fetch_loop_common { my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc); my $longest_path = longest_common_path($gsv, $globs); my $ra_url = $self->{url}; + my $find_trailing_edge; while (1) { my %revs; my $err; @@ -4398,8 +4494,10 @@ sub gs_fetch_loop_common { sub { $revs{$_[1]} = _cb(@_) }); if ($err) { print "Checked through r$max\r"; + } else { + $find_trailing_edge = 1; } - if ($err && $max >= $head) { + if ($err and $find_trailing_edge) { print STDERR "Path '$longest_path' ", "was probably deleted:\n", $err->expanded_message, @@ -4411,13 +4509,14 @@ sub gs_fetch_loop_common { my $ok; $self->get_log([$longest_path], $min, $hi, 0, 1, 1, sub { - $ok ||= $_[1]; + $ok = $_[1]; $revs{$_[1]} = _cb(@_) }); if ($ok) { print STDERR "r$min .. r$ok OK\n"; last; } } + $find_trailing_edge = 0; } $SVN::Error::handler = $err_handler; @@ -4987,11 +5086,22 @@ sub cmd_blame { '--', $path); my ($sha1); my %authors; + my @buffer; + my %dsha; #distinct sha keys + while (my $line = <$fh>) { + push @buffer, $line; + if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) { + $dsha{$1} = 1; + } + } + + my $s2r = ::cmt_sha2rev_batch([keys %dsha]); + + foreach my $line (@buffer) { if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) { - $sha1 = $1; - (undef, $rev, undef) = ::cmt_metadata($1); - $rev = '0' if (!$rev); + $rev = $s2r->{$1}; + $rev = '0' if (!$rev) } elsif ($line =~ /^author (.*)/) { $authors{$rev} = $1; diff --git a/git-web--browse.sh b/git-web--browse.sh index 7ed0faddcd..4f5c740df5 100755 --- a/git-web--browse.sh +++ b/git-web--browse.sh @@ -161,9 +161,12 @@ case "$browser" in ;; esac ;; - w3m|links|lynx|open|start) + w3m|links|lynx|open) eval "$browser_path" "$@" ;; + start) + exec "$browser_path" '"web-browse"' "$@" + ;; dillo) "$browser_path" "$@" & ;; @@ -5,7 +5,7 @@ #include "run-command.h" const char git_usage_string[] = - "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; + "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]"; const char git_more_info_string[] = "See 'git help COMMAND' for more information on a specific command."; @@ -47,7 +47,7 @@ static void commit_pager_choice(void) { } } -static int handle_options(const char*** argv, int* argc, int* envchanged) +static int handle_options(const char ***argv, int *argc, int *envchanged) { int handled = 0; @@ -75,6 +75,9 @@ static int handle_options(const char*** argv, int* argc, int* envchanged) puts(git_exec_path()); exit(0); } + } else if (!strcmp(cmd, "--html-path")) { + puts(system_path(GIT_HTML_PATH)); + exit(0); } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) { use_pager = 1; } else if (!strcmp(cmd, "--no-pager")) { @@ -133,7 +136,7 @@ static int handle_alias(int *argcp, const char ***argv) int envchanged = 0, ret = 0, saved_errno = errno; const char *subdir; int count, option_count; - const char** new_argv; + const char **new_argv; const char *alias_command; char *alias_string; int unused_nongit; @@ -184,10 +187,10 @@ static int handle_alias(int *argcp, const char ***argv) "trace: alias expansion: %s =>", alias_command); - new_argv = xrealloc(new_argv, sizeof(char*) * + new_argv = xrealloc(new_argv, sizeof(char *) * (count + *argcp + 1)); /* insert after command name */ - memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp); + memcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp); new_argv[count+*argcp] = NULL; *argv = new_argv; @@ -271,6 +274,7 @@ static void handle_internal_command(int argc, const char **argv) { "annotate", cmd_annotate, RUN_SETUP }, { "apply", cmd_apply }, { "archive", cmd_archive }, + { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE }, { "blame", cmd_blame, RUN_SETUP }, { "branch", cmd_branch, RUN_SETUP }, { "bundle", cmd_bundle }, @@ -323,6 +327,7 @@ static void handle_internal_command(int argc, const char **argv) { "merge-ours", cmd_merge_ours, RUN_SETUP }, { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE }, + { "mktree", cmd_mktree, RUN_SETUP }, { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, @@ -493,7 +498,7 @@ int main(int argc, const char **argv) /* * We use PATH to find git commands, but we prepend some higher - * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH + * precedence paths: the "--exec-path" option, the GIT_EXEC_PATH * environment, and the $(gitexecdir) from the Makefile at build * time. */ diff --git a/gitk-git/gitk b/gitk-git/gitk index 1773ae63eb..4604c831fe 100644 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -187,7 +187,8 @@ proc parseviewargs {n arglist} { "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" - "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" - "--remove-empty" - "--first-parent" - "--cherry-pick" - - "-S*" - "--pickaxe-all" - "--pickaxe-regex" { + "-S*" - "--pickaxe-all" - "--pickaxe-regex" - + "--simplify-by-decoration" { # These mean that we get a subset of the commits set filtered 1 lappend glflags $arg @@ -521,7 +522,7 @@ proc updatecommits {} { incr viewactive($view) set viewcomplete($view) 0 reset_pending_select {} - nowbusy $view "Reading" + nowbusy $view [mc "Reading"] if {$showneartags} { getallcommits } @@ -1830,7 +1831,9 @@ proc setoptions {} { option add *Button.font uifont startupFile option add *Checkbutton.font uifont startupFile option add *Radiobutton.font uifont startupFile - option add *Menu.font uifont startupFile + if {[tk windowingsystem] ne "aqua"} { + option add *Menu.font uifont startupFile + } option add *Menubutton.font uifont startupFile option add *Label.font uifont startupFile option add *Message.font uifont startupFile @@ -1910,8 +1913,8 @@ proc makewindow {} { # The "mc" arguments here are purely so that xgettext # sees the following string as needing to be translated - makemenu .bar { - {mc "File" cascade { + set file { + mc "File" cascade { {mc "Update" command updatecommits -accelerator F5} {mc "Reload" command reloadcommits -accelerator Meta1-F5} {mc "Reread references" command rereadrefs} @@ -1921,21 +1924,41 @@ proc makewindow {} { {xx "" separator} {mc "Quit" command doquit -accelerator Meta1-Q} }} - {mc "Edit" cascade { + set edit { + mc "Edit" cascade { {mc "Preferences" command doprefs} }} - {mc "View" cascade { + set view { + mc "View" cascade { {mc "New view..." command {newview 0} -accelerator Shift-F4} {mc "Edit view..." command editview -state disabled -accelerator F4} {mc "Delete view" command delview -state disabled} {xx "" separator} {mc "All files" radiobutton {selectedview 0} -command {showview 0}} }} - {mc "Help" cascade { + if {[tk windowingsystem] ne "aqua"} { + set help { + mc "Help" cascade { + {mc "About gitk" command about} + {mc "Key bindings" command keys} + }} + set bar [list $file $edit $view $help] + } else { + proc ::tk::mac::ShowPreferences {} {doprefs} + proc ::tk::mac::Quit {} {doquit} + lset file end [lreplace [lindex $file end] end-1 end] + set apple { + xx "Apple" cascade { {mc "About gitk" command about} + {xx "" separator} + }} + set help { + mc "Help" cascade { {mc "Key bindings" command keys} }} + set bar [list $apple $file $view $help] } + makemenu .bar $bar . configure -menu .bar # the gui has upper and lower half, parts of a paned window. @@ -2123,7 +2146,7 @@ proc makewindow {} { label .bleft.mid.labeldiffcontext -text " [mc "Lines of context"]: " pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left spinbox .bleft.mid.diffcontext -width 5 -font textfont \ - -from 1 -increment 1 -to 10000000 \ + -from 0 -increment 1 -to 10000000 \ -validate all -validatecommand "diffcontextvalidate %P" \ -textvariable diffcontextstring .bleft.mid.diffcontext set $diffcontext @@ -2229,10 +2252,16 @@ proc makewindow {} { } } + if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} { + wm state . $geometry(state) + } + if {[tk windowingsystem] eq {aqua}} { set M1B M1 + set ::BM "3" } else { set M1B Control + set ::BM "2" } bind .pwbottom <Configure> {resizecdetpanes %W %w} @@ -2250,10 +2279,14 @@ proc makewindow {} { set delta [expr {- (%D)}] allcanvs yview scroll $delta units } + bindall <Shift-MouseWheel> { + set delta [expr {- (%D)}] + $canv xview scroll $delta units + } } } - bindall <2> "canvscan mark %W %x %y" - bindall <B2-Motion> "canvscan dragto %W %x %y" + bindall <$::BM> "canvscan mark %W %x %y" + bindall <B$::BM-Motion> "canvscan dragto %W %x %y" bindkey <Home> selfirstline bindkey <End> sellastline bind . <Key-Up> "selnextline -1" @@ -2285,6 +2318,7 @@ proc makewindow {} { bindkey d "$ctext yview scroll 18 units" bindkey u "$ctext yview scroll -18 units" bindkey / {focus $fstring} + bindkey <Key-KP_Divide> {focus $fstring} bindkey <Key-Return> {dofind 1 1} bindkey ? {dofind -1 1} bindkey f nextfile @@ -2331,6 +2365,10 @@ proc makewindow {} { {mc "Create new branch" command mkbranch} {mc "Cherry-pick this commit" command cherrypick} {mc "Reset HEAD branch to here" command resethead} + {mc "Mark this commit" command markhere} + {mc "Return to mark" command gotomark} + {mc "Find descendant of this and mark" command find_common_desc} + {mc "Compare with marked commit" command compare_commits} } $rowctxmenu configure -tearoff 0 @@ -2487,6 +2525,9 @@ proc savestuff {w} { if {![winfo viewable .]} return catch { set f [open "~/.gitk-new" w] + if {$::tcl_platform(platform) eq {windows}} { + file attributes "~/.gitk-new" -hidden true + } puts $f [list set mainfont $mainfont] puts $f [list set textfont $textfont] puts $f [list set uifont $uifont] @@ -2512,6 +2553,7 @@ proc savestuff {w} { puts $f [list set perfile_attrs $perfile_attrs] puts $f "set geometry(main) [wm geometry .]" + puts $f "set geometry(state) [wm state .]" puts $f "set geometry(topwidth) [winfo width .tf]" puts $f "set geometry(topheight) [winfo height .tf]" puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\"" @@ -3204,9 +3246,8 @@ proc external_diff {} { set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir] if {$difffromfile ne {} && $difftofile ne {}} { - set cmd [concat | [shellsplit $extdifftool] \ - [list $difffromfile $difftofile]] - if {[catch {set fl [open $cmd r]} err]} { + set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] + if {[catch {set fl [open |$cmd r]} err]} { file delete -force $diffdir error_popup "$extdifftool: [mc "command failed:"] $err" } else { @@ -3631,17 +3672,36 @@ proc newview {ishighlight} { } set known_view_options { - {perm b . {} {mc "Remember this view"}} - {args t50= + {} {mc "Commits to include (arguments to git log):"}} - {all b * "--all" {mc "Use all refs"}} - {dorder b . {"--date-order" "-d"} {mc "Strictly sort by date"}} - {lright b . "--left-right" {mc "Mark branch sides"}} - {since t15 + {"--since=*" "--after=*"} {mc "Since date:"}} - {until t15 . {"--until=*" "--before=*"} {mc "Until date:"}} - {limit t10 + "--max-count=*" {mc "Max count:"}} - {skip t10 . "--skip=*" {mc "Skip:"}} - {first b . "--first-parent" {mc "Limit to first parent"}} - {cmd t50= + {} {mc "Command to generate more commits to include:"}} + {perm b . {} {mc "Remember this view"}} + {reflabel l + {} {mc "References (space separated list):"}} + {refs t15 .. {} {mc "Branches & tags:"}} + {allrefs b *. "--all" {mc "All refs"}} + {branches b . "--branches" {mc "All (local) branches"}} + {tags b . "--tags" {mc "All tags"}} + {remotes b . "--remotes" {mc "All remote-tracking branches"}} + {commitlbl l + {} {mc "Commit Info (regular expressions):"}} + {author t15 .. "--author=*" {mc "Author:"}} + {committer t15 . "--committer=*" {mc "Committer:"}} + {loginfo t15 .. "--grep=*" {mc "Commit Message:"}} + {allmatch b .. "--all-match" {mc "Matches all Commit Info criteria"}} + {changes_l l + {} {mc "Changes to Files:"}} + {pickaxe_s r0 . {} {mc "Fixed String"}} + {pickaxe_t r1 . "--pickaxe-regex" {mc "Regular Expression"}} + {pickaxe t15 .. "-S*" {mc "Search string:"}} + {datelabel l + {} {mc "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 15:27:38\"):"}} + {since t15 .. {"--since=*" "--after=*"} {mc "Since:"}} + {until t15 . {"--until=*" "--before=*"} {mc "Until:"}} + {limit_lbl l + {} {mc "Limit and/or skip a number of revisions (positive integer):"}} + {limit t10 *. "--max-count=*" {mc "Number to show:"}} + {skip t10 . "--skip=*" {mc "Number to skip:"}} + {misc_lbl l + {} {mc "Miscellaneous options:"}} + {dorder b *. {"--date-order" "-d"} {mc "Strictly sort by date"}} + {lright b . "--left-right" {mc "Mark branch sides"}} + {first b . "--first-parent" {mc "Limit to first parent"}} + {smplhst b . "--simplify-by-decoration" {mc "Simple history"}} + {args t50 *. {} {mc "Additional arguments to git log:"}} + {allpaths path + {} {mc "Enter files and directories to include, one per line:"}} + {cmd t50= + {} {mc "Command to generate more commits to include:"}} } proc encode_view_opts {n} { @@ -3653,13 +3713,19 @@ proc encode_view_opts {n} { if {$patterns eq {}} continue set pattern [lindex $patterns 0] - set val $newviewopts($n,[lindex $opt 0]) - if {[lindex $opt 1] eq "b"} { + set val $newviewopts($n,[lindex $opt 0]) if {$val} { lappend rargs $pattern } + } elseif {[regexp {^r(\d+)$} [lindex $opt 1] type value]} { + regexp {^(.*_)} [lindex $opt 0] uselessvar button_id + set val $newviewopts($n,$button_id) + if {$val eq $value} { + lappend rargs $pattern + } } else { + set val $newviewopts($n,[lindex $opt 0]) set val [string trim $val] if {$val ne {}} { set pfix [string range $pattern 0 end-1] @@ -3667,6 +3733,7 @@ proc encode_view_opts {n} { } } } + set rargs [concat $rargs [shellsplit $newviewopts($n,refs)]] return [concat $rargs [shellsplit $newviewopts($n,args)]] } @@ -3674,14 +3741,22 @@ proc decode_view_opts {n view_args} { global known_view_options newviewopts foreach opt $known_view_options { + set id [lindex $opt 0] if {[lindex $opt 1] eq "b"} { + # Checkboxes + set val 0 + } elseif {[regexp {^r(\d+)$} [lindex $opt 1]]} { + # Radiobuttons + regexp {^(.*_)} $id uselessvar id set val 0 } else { + # Text fields set val {} } - set newviewopts($n,[lindex $opt 0]) $val + set newviewopts($n,$id) $val } set oargs [list] + set refargs [list] foreach arg $view_args { if {[regexp -- {^-([0-9]+)$} $arg arg cnt] && ![info exists found(limit)]} { @@ -3695,11 +3770,17 @@ proc decode_view_opts {n view_args} { if {[info exists found($id)]} continue foreach pattern [lindex $opt 3] { if {![string match $pattern $arg]} continue - if {[lindex $opt 1] ne "b"} { + if {[lindex $opt 1] eq "b"} { + # Check buttons + set val 1 + } elseif {[regexp {^r(\d+)$} [lindex $opt 1] match num]} { + # Radio buttons + regexp {^(.*_)} $id uselessvar id + set val $num + } else { + # Text input fields set size [string length $pattern] set val [string range $arg [expr {$size-1}] end] - } else { - set val 1 } set newviewopts($n,$id) $val set found($id) 1 @@ -3708,8 +3789,13 @@ proc decode_view_opts {n view_args} { if {[info exists val]} break } if {[info exists val]} continue - lappend oargs $arg + if {[regexp {^-} $arg]} { + lappend oargs $arg + } else { + lappend refargs $arg + } } + set newviewopts($n,refs) [shellarglist $refargs] set newviewopts($n,args) [shellarglist $oargs] } @@ -3737,7 +3823,7 @@ proc editview {} { set newviewopts($curview,perm) $viewperm($curview) set newviewopts($curview,cmd) $viewargscmd($curview) decode_view_opts $curview $viewargs($curview) - vieweditor $top $curview "Gitk: edit view $viewname($curview)" + vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)" } proc vieweditor {top n title} { @@ -3745,16 +3831,16 @@ proc vieweditor {top n title} { global known_view_options toplevel $top - wm title $top $title + wm title $top [concat $title [mc "-- criteria for selecting revisions"]] make_transient $top . # View name frame $top.nfr - label $top.nl -text [mc "Name"] + label $top.nl -text [mc "View Name:"] entry $top.name -width 20 -textvariable newviewname($n) pack $top.nfr -in $top -fill x -pady 5 -padx 3 - pack $top.nl -in $top.nfr -side left -padx {0 30} - pack $top.name -in $top.nfr -side left + pack $top.nl -in $top.nfr -side left -padx {0 5} + pack $top.name -in $top.nfr -side left -padx {0 25} # View options set cframe $top.nfr @@ -3773,14 +3859,28 @@ proc vieweditor {top n title} { frame $cframe pack $cframe -in $top -fill x -pady 3 -padx 3 set cexpand [expr {$flags eq "*"}] + } elseif {$flags eq ".." || $flags eq "*."} { + set cframe $top.fr$cnt + incr cnt + frame $cframe + pack $cframe -in $top -fill x -pady 3 -padx [list 15 3] + set cexpand [expr {$flags eq "*."}] } else { set lxpad 5 } - if {$type eq "b"} { + if {$type eq "l"} { + label $cframe.l_$id -text $title + pack $cframe.l_$id -in $cframe -side left -pady [list 3 0] -anchor w + } elseif {$type eq "b"} { checkbutton $cframe.c_$id -text $title -variable newviewopts($n,$id) pack $cframe.c_$id -in $cframe -side left \ -padx [list $lxpad 0] -expand $cexpand -anchor w + } elseif {[regexp {^r(\d+)$} $type type sz]} { + regexp {^(.*_)} $id uselessvar button_id + radiobutton $cframe.c_$id -text $title -variable newviewopts($n,$button_id) -value $sz + pack $cframe.c_$id -in $cframe -side left \ + -padx [list $lxpad 0] -expand $cexpand -anchor w } elseif {[regexp {^t(\d+)$} $type type sz]} { message $cframe.l_$id -aspect 1500 -text $title entry $cframe.e_$id -width $sz -background $bgcolor \ @@ -3793,23 +3893,22 @@ proc vieweditor {top n title} { -textvariable newviewopts($n,$id) pack $cframe.l_$id -in $cframe -side top -pady [list 3 0] -anchor w pack $cframe.e_$id -in $cframe -side top -fill x + } elseif {$type eq "path"} { + message $top.l -aspect 1500 -text $title + pack $top.l -in $top -side top -pady [list 3 0] -anchor w -padx 3 + text $top.t -width 40 -height 5 -background $bgcolor -font uifont + if {[info exists viewfiles($n)]} { + foreach f $viewfiles($n) { + $top.t insert end $f + $top.t insert end "\n" + } + $top.t delete {end - 1c} end + $top.t mark set insert 0.0 + } + pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3 } } - # Path list - message $top.l -aspect 1500 \ - -text [mc "Enter files and directories to include, one per line:"] - pack $top.l -in $top -side top -pady [list 7 0] -anchor w -padx 3 - text $top.t -width 40 -height 5 -background $bgcolor -font uifont - if {[info exists viewfiles($n)]} { - foreach f $viewfiles($n) { - $top.t insert end $f - $top.t insert end "\n" - } - $top.t delete {end - 1c} end - $top.t mark set insert 0.0 - } - pack $top.t -in $top -side top -pady [list 0 5] -fill both -expand 1 -padx 3 frame $top.buts button $top.buts.ok -text [mc "OK"] -command [list newviewok $top $n] button $top.buts.apply -text [mc "Apply (F5)"] -command [list newviewok $top $n 1] @@ -4046,7 +4145,7 @@ proc ishighlighted {id} { } proc bolden {id font} { - global canv linehtag currentid boldids need_redisplay + global canv linehtag currentid boldids need_redisplay markedid # need_redisplay = 1 means the display is stale and about to be redrawn if {$need_redisplay} return @@ -4059,6 +4158,9 @@ proc bolden {id font} { -fill [$canv cget -selectbackground]] $canv lower $t } + if {[info exists markedid] && $id eq $markedid} { + make_idmark $id + } } proc bolden_name {id font} { @@ -5563,7 +5665,7 @@ proc drawcmittext {id row col} { global cmitlisted commitinfo rowidlist parentlist global rowtextx idpos idtags idheads idotherrefs global linehtag linentag linedtag selectedline - global canvxmax boldids boldnameids fgcolor + global canvxmax boldids boldnameids fgcolor markedid global mainheadid nullid nullid2 circleitem circlecolors ctxbut # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right @@ -5645,6 +5747,9 @@ proc drawcmittext {id row col} { if {$selectedline == $row} { make_secsel $id } + if {[info exists markedid] && $markedid eq $id} { + make_idmark $id + } set xr [expr {$xt + [font measure $font $headline]}] if {$xr > $canvxmax} { set canvxmax $xr @@ -6443,6 +6548,17 @@ proc setlink {id lk} { } } +proc appendshortlink {id {pre {}} {post {}}} { + global ctext linknum + + $ctext insert end $pre + $ctext tag delete link$linknum + $ctext insert end [string range $id 0 7] link$linknum + $ctext insert end $post + setlink $id link$linknum + incr linknum +} + proc makelink {id} { global pendinglinks @@ -6499,7 +6615,7 @@ proc appendrefs {pos ids var} { } } if {[llength $tags] > $maxrefs} { - $ctext insert $pos "many ([llength $tags])" + $ctext insert $pos "[mc "many"] ([llength $tags])" } else { set tags [lsort -index 0 -decreasing $tags] set sep {} @@ -6586,6 +6702,16 @@ proc make_secsel {id} { $canv3 lower $t } +proc make_idmark {id} { + global linehtag canv fgcolor + + if {![info exists linehtag($id)]} return + $canv delete markid + set t [eval $canv create rect [$canv bbox $linehtag($id)] \ + -tags markid -outline $fgcolor] + $canv raise $t +} + proc selectline {l isnew {desired_loc {}}} { global canv ctext commitinfo selectedline global canvy0 linespc parents children curview @@ -7182,7 +7308,7 @@ proc diffcontextchange {n1 n2 op} { global diffcontextstring diffcontext if {[string is integer -strict $diffcontextstring]} { - if {$diffcontextstring > 0} { + if {$diffcontextstring >= 0} { set diffcontext $diffcontextstring reselectline } @@ -7200,8 +7326,13 @@ proc getblobdiffs {ids} { global ignorespace global limitdiffs vfilelimit curview global diffencoding targetline diffnparents + global git_version - set cmd [diffcmd $ids "-p -C --cc --no-commit-id -U$diffcontext"] + set textconv {} + if {[package vcompare $git_version "1.6.1"] >= 0} { + set textconv "--textconv" + } + set cmd [diffcmd $ids "-p $textconv -C --cc --no-commit-id -U$diffcontext"] if {$ignorespace} { append cmd " -w" } @@ -7216,7 +7347,7 @@ proc getblobdiffs {ids} { set diffnparents 0 set diffinhdr 0 set diffencoding [get_path_encoding {}] - fconfigure $bdf -blocking 0 -encoding binary + fconfigure $bdf -blocking 0 -encoding binary -eofchar {} set blobdifffd($ids) $bdf filerun $bdf [list getblobdiffline $bdf $diffids] } @@ -7367,7 +7498,8 @@ proc getblobdiffline {bdf ids} { $ctext insert end "$line\n" filesep } else { - set line [encoding convertfrom $diffencoding $line] + set line [string map {\x1A ^Z} \ + [encoding convertfrom $diffencoding $line]] # parse the prefix - one ' ', '-' or '+' for each parent set prefix [string range $line 0 [expr {$diffnparents - 1}]] set tag [expr {$diffnparents > 1? "m": "d"}] @@ -7970,7 +8102,7 @@ proc mstime {} { proc rowmenu {x y id} { global rowctxmenu selectedline rowmenuid curview - global nullid nullid2 fakerowmenu mainhead + global nullid nullid2 fakerowmenu mainhead markedid stopfinding set rowmenuid $id @@ -7986,6 +8118,15 @@ proc rowmenu {x y id} { } else { $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled } + if {[info exists markedid] && $markedid ne $id} { + $menu entryconfigure 9 -state normal + $menu entryconfigure 10 -state normal + $menu entryconfigure 11 -state normal + } else { + $menu entryconfigure 9 -state disabled + $menu entryconfigure 10 -state disabled + $menu entryconfigure 11 -state disabled + } } else { set menu $fakerowmenu } @@ -7995,6 +8136,162 @@ proc rowmenu {x y id} { tk_popup $menu $x $y } +proc markhere {} { + global rowmenuid markedid canv + + set markedid $rowmenuid + make_idmark $markedid +} + +proc gotomark {} { + global markedid + + if {[info exists markedid]} { + selbyid $markedid + } +} + +proc replace_by_kids {l r} { + global curview children + + set id [commitonrow $r] + set l [lreplace $l 0 0] + foreach kid $children($curview,$id) { + lappend l [rowofcommit $kid] + } + return [lsort -integer -decreasing -unique $l] +} + +proc find_common_desc {} { + global markedid rowmenuid curview children + + if {![info exists markedid]} return + if {![commitinview $markedid $curview] || + ![commitinview $rowmenuid $curview]} return + #set t1 [clock clicks -milliseconds] + set l1 [list [rowofcommit $markedid]] + set l2 [list [rowofcommit $rowmenuid]] + while 1 { + set r1 [lindex $l1 0] + set r2 [lindex $l2 0] + if {$r1 eq {} || $r2 eq {}} break + if {$r1 == $r2} { + selectline $r1 1 + break + } + if {$r1 > $r2} { + set l1 [replace_by_kids $l1 $r1] + } else { + set l2 [replace_by_kids $l2 $r2] + } + } + #set t2 [clock clicks -milliseconds] + #puts "took [expr {$t2-$t1}]ms" +} + +proc compare_commits {} { + global markedid rowmenuid curview children + + if {![info exists markedid]} return + if {![commitinview $markedid $curview]} return + addtohistory [list do_cmp_commits $markedid $rowmenuid] + do_cmp_commits $markedid $rowmenuid +} + +proc getpatchid {id} { + global patchids + + if {![info exists patchids($id)]} { + set cmd [diffcmd [list $id] {-p --root}] + # trim off the initial "|" + set cmd [lrange $cmd 1 end] + if {[catch { + set x [eval exec $cmd | git patch-id] + set patchids($id) [lindex $x 0] + }]} { + set patchids($id) "error" + } + } + return $patchids($id) +} + +proc do_cmp_commits {a b} { + global ctext curview parents children patchids commitinfo + + $ctext conf -state normal + clear_ctext + init_flist {} + for {set i 0} {$i < 100} {incr i} { + set skipa 0 + set skipb 0 + if {[llength $parents($curview,$a)] > 1} { + appendshortlink $a [mc "Skipping merge commit "] "\n" + set skipa 1 + } else { + set patcha [getpatchid $a] + } + if {[llength $parents($curview,$b)] > 1} { + appendshortlink $b [mc "Skipping merge commit "] "\n" + set skipb 1 + } else { + set patchb [getpatchid $b] + } + if {!$skipa && !$skipb} { + set heada [lindex $commitinfo($a) 0] + set headb [lindex $commitinfo($b) 0] + if {$patcha eq "error"} { + appendshortlink $a [mc "Error getting patch ID for "] \ + [mc " - stopping\n"] + break + } + if {$patchb eq "error"} { + appendshortlink $b [mc "Error getting patch ID for "] \ + [mc " - stopping\n"] + break + } + if {$patcha eq $patchb} { + if {$heada eq $headb} { + appendshortlink $a [mc "Commit "] + appendshortlink $b " == " " $heada\n" + } else { + appendshortlink $a [mc "Commit "] " $heada\n" + appendshortlink $b [mc " is the same patch as\n "] \ + " $headb\n" + } + set skipa 1 + set skipb 1 + } else { + $ctext insert end "\n" + appendshortlink $a [mc "Commit "] " $heada\n" + appendshortlink $b [mc " differs from\n "] \ + " $headb\n" + $ctext insert end [mc "- stopping\n"] + break + } + } + if {$skipa} { + if {[llength $children($curview,$a)] != 1} { + $ctext insert end "\n" + appendshortlink $a [mc "Commit "] \ + [mc " has %s children - stopping\n" \ + [llength $children($curview,$a)]] + break + } + set a [lindex $children($curview,$a) 0] + } + if {$skipb} { + if {[llength $children($curview,$b)] != 1} { + appendshortlink $b [mc "Commit "] \ + [mc " has %s children - stopping\n" \ + [llength $children($curview,$b)]] + break + } + set b [lindex $children($curview,$b) 0] + } + } + $ctext conf -state disabled +} + proc diffvssel {dirn} { global rowmenuid selectedline @@ -8189,7 +8486,7 @@ proc domktag {} { } proc redrawtags {id} { - global canv linehtag idpos currentid curview cmitlisted + global canv linehtag idpos currentid curview cmitlisted markedid global canvxmax iddrawn circleitem mainheadid circlecolors if {![commitinview $id $curview]} return @@ -8214,6 +8511,9 @@ proc redrawtags {id} { if {[info exists currentid] && $currentid == $id} { make_secsel $id } + if {[info exists markedid] && $markedid eq $id} { + make_idmark $id + } } proc mktagcan {} { @@ -10197,7 +10497,7 @@ proc doprefs {} { proc choose_extdiff {} { global extdifftool - set prog [tk_getOpenFile -title "External diff tool" -multiple false] + set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false] if {$prog ne {}} { set extdifftool $prog } @@ -10240,6 +10540,7 @@ proc setfg {c} { } allcanvs itemconf text -fill $c $canv itemconf circle -outline $c + $canv itemconf markid -outline $c } proc prefscan {} { @@ -10689,9 +10990,15 @@ catch { } } -set mainfont {Helvetica 9} -set textfont {Courier 9} -set uifont {Helvetica 9 bold} +if {[tk windowingsystem] eq "aqua"} { + set mainfont {{Lucida Grande} 9} + set textfont {Monaco 9} + set uifont {{Lucida Grande} 9 bold} +} else { + set mainfont {Helvetica 9} + set textfont {Courier 9} + set uifont {Helvetica 9 bold} +} set tabstop 8 set findmergefiles 0 set maxgraphpct 50 @@ -10712,7 +11019,11 @@ set datetimeformat "%Y-%m-%d %H:%M:%S" set autoselect 1 set perfile_attrs 0 -set extdifftool "meld" +if {[tk windowingsystem] eq "aqua"} { + set extdifftool "opendiff" +} else { + set extdifftool "meld" +} set colors {green red blue magenta darkgrey brown orange} set bgcolor white @@ -10843,6 +11154,7 @@ set nullid2 "0000000000000000000000000000000000000001" set nullfile "/dev/null" set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}] +set git_version [join [lrange [split [lindex [exec git version] end] .] 0 2] .] set runq {} set history {} @@ -10883,9 +11195,33 @@ set lserial 0 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}] setcoords makewindow +catch { + image create photo gitlogo -width 16 -height 16 + + image create photo gitlogominus -width 4 -height 2 + gitlogominus put #C00000 -to 0 0 4 2 + gitlogo copy gitlogominus -to 1 5 + gitlogo copy gitlogominus -to 6 5 + gitlogo copy gitlogominus -to 11 5 + image delete gitlogominus + + image create photo gitlogoplus -width 4 -height 4 + gitlogoplus put #008000 -to 1 0 3 4 + gitlogoplus put #008000 -to 0 1 4 3 + gitlogo copy gitlogoplus -to 1 9 + gitlogo copy gitlogoplus -to 6 9 + gitlogo copy gitlogoplus -to 11 9 + image delete gitlogoplus + + image create photo gitlogo32 -width 32 -height 32 + gitlogo32 copy gitlogo -zoom 2 2 + + wm iconphoto . -default gitlogo gitlogo32 +} # wait for the window to become visible tkwait visibility . wm title . "[file tail $argv0]: [file tail [pwd]]" +update readrefs if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} { diff --git a/gitk-git/po/de.po b/gitk-git/po/de.po index 825dc98f74..53ef0d6359 100644 --- a/gitk-git/po/de.po +++ b/gitk-git/po/de.po @@ -1,14 +1,15 @@ # Translation of gitk to German. # Copyright (C) 2007 Paul Mackerras. # This file is distributed under the same license as the gitk package. -# Christian Stimming <stimming@tuhh.de>, 2007 # +# Christian Stimming <stimming@tuhh.de>, 2007. +# Frederik Schwarzer <schwarzerf@gmail.com>, 2008. msgid "" msgstr "" "Project-Id-Version: git-gui\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2008-12-06 20:40+0100\n" -"PO-Revision-Date: 2008-12-06 20:45+0100\n" +"POT-Creation-Date: 2009-05-12 21:55+0200\n" +"PO-Revision-Date: 2009-05-12 22:18+0200\n" "Last-Translator: Christian Stimming <stimming@tuhh.de>\n" "Language-Team: German\n" "MIME-Version: 1.0\n" @@ -17,297 +18,314 @@ msgstr "" #: gitk:113 msgid "Couldn't get list of unmerged files:" -msgstr "Liste der nicht-zusammengeführten Dateien nicht gefunden:" +msgstr "Liste der nicht zusammengeführten Dateien nicht gefunden:" -#: gitk:272 +#: gitk:268 msgid "Error parsing revisions:" msgstr "Fehler beim Laden der Versionen:" -#: gitk:327 +#: gitk:323 msgid "Error executing --argscmd command:" -msgstr "Fehler beim --argscmd Kommando:" +msgstr "Fehler beim Ausführen des --argscmd-Kommandos:" -#: gitk:340 +#: gitk:336 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" -"Keine Dateien ausgewählt: --merge angegeben, es existieren aber keine nicht-" -"zusammengeführten Dateien." +"Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es existieren " +"keine nicht zusammengeführten Dateien." -#: gitk:343 +#: gitk:339 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." msgstr "" -"Keine Dateien ausgewähle: --merge angegeben, aber keine nicht-" -"zusammengeführten Dateien sind in der Dateiauswahl." +"Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es sind keine " +"nicht zusammengeführten Dateien in der Dateiauswahl." -#: gitk:365 gitk:503 +#: gitk:361 gitk:508 msgid "Error executing git log:" -msgstr "Fehler beim Ausführen von git-log:" +msgstr "Fehler beim Ausführen von »git log«:" -#: gitk:378 +#: gitk:379 gitk:524 msgid "Reading" msgstr "Lesen" -#: gitk:438 gitk:3462 +#: gitk:439 gitk:4061 msgid "Reading commits..." -msgstr "Versionen lesen..." +msgstr "Versionen werden gelesen ..." -#: gitk:441 gitk:1528 gitk:3465 +#: gitk:442 gitk:1560 gitk:4064 msgid "No commits selected" -msgstr "Keine Versionen ausgewählt." +msgstr "Keine Versionen ausgewählt" -#: gitk:1399 +#: gitk:1436 msgid "Can't parse git log output:" -msgstr "Ausgabe von git-log kann nicht erkannt werden:" +msgstr "Ausgabe von »git log« kann nicht erkannt werden:" -#: gitk:1605 +#: gitk:1656 msgid "No commit information available" msgstr "Keine Versionsinformation verfügbar" -#: gitk:1709 gitk:1731 gitk:3259 gitk:7764 gitk:9293 gitk:9466 +#: gitk:1791 gitk:1815 gitk:3854 gitk:8714 gitk:10250 gitk:10422 msgid "OK" msgstr "Ok" -#: gitk:1733 gitk:3260 gitk:7439 gitk:7510 gitk:7613 gitk:7660 gitk:7766 -#: gitk:9294 gitk:9467 +#: gitk:1817 gitk:3856 gitk:8311 gitk:8385 gitk:8495 gitk:8544 gitk:8716 +#: gitk:10251 gitk:10423 msgid "Cancel" msgstr "Abbrechen" -#: gitk:1811 +#: gitk:1917 msgid "Update" msgstr "Aktualisieren" -#: gitk:1812 +#: gitk:1918 msgid "Reload" msgstr "Neu laden" -#: gitk:1813 +#: gitk:1919 msgid "Reread references" msgstr "Zweige neu laden" -#: gitk:1814 +#: gitk:1920 msgid "List references" msgstr "Zweige/Markierungen auflisten" -#: gitk:1915 +#: gitk:1922 msgid "Start git gui" msgstr "»git gui« starten" -#: gitk:1917 +#: gitk:1924 msgid "Quit" msgstr "Beenden" -#: gitk:1810 +#: gitk:1916 msgid "File" msgstr "Datei" -#: gitk:1818 +#: gitk:1928 msgid "Preferences" msgstr "Einstellungen" -#: gitk:1817 +#: gitk:1927 msgid "Edit" msgstr "Bearbeiten" -#: gitk:1821 +#: gitk:1932 msgid "New view..." -msgstr "Neue Ansicht..." +msgstr "Neue Ansicht ..." -#: gitk:1822 +#: gitk:1933 msgid "Edit view..." -msgstr "Ansicht bearbeiten..." +msgstr "Ansicht bearbeiten ..." -#: gitk:1823 +#: gitk:1934 msgid "Delete view" -msgstr "Ansicht löschen" +msgstr "Ansicht entfernen" -#: gitk:1825 +#: gitk:1936 msgid "All files" msgstr "Alle Dateien" -#: gitk:1820 gitk:3196 +#: gitk:1931 gitk:3666 msgid "View" msgstr "Ansicht" -#: gitk:1828 gitk:2487 +#: gitk:1941 gitk:1951 gitk:2650 msgid "About gitk" msgstr "Ãœber gitk" -#: gitk:1829 +#: gitk:1942 gitk:1956 msgid "Key bindings" msgstr "Tastenkürzel" -#: gitk:1827 +#: gitk:1940 gitk:1955 msgid "Help" msgstr "Hilfe" -#: gitk:1887 +#: gitk:2016 msgid "SHA1 ID: " msgstr "SHA1:" -#: gitk:1918 +#: gitk:2047 msgid "Row" msgstr "Zeile" -#: gitk:1949 +#: gitk:2078 msgid "Find" msgstr "Suche" -#: gitk:1950 +#: gitk:2079 msgid "next" msgstr "nächste" -#: gitk:1951 +#: gitk:2080 msgid "prev" msgstr "vorige" -#: gitk:1952 +#: gitk:2081 msgid "commit" msgstr "Version nach" -#: gitk:1955 gitk:1957 gitk:3617 gitk:3640 gitk:3664 gitk:5550 gitk:5621 +#: gitk:2084 gitk:2086 gitk:4222 gitk:4245 gitk:4269 gitk:6210 gitk:6282 +#: gitk:6366 msgid "containing:" msgstr "Beschreibung:" -#: gitk:1958 gitk:2954 gitk:2959 gitk:3692 +#: gitk:2087 gitk:3158 gitk:3163 gitk:4297 msgid "touching paths:" msgstr "Dateien:" -#: gitk:1959 gitk:3697 +#: gitk:2088 gitk:4302 msgid "adding/removing string:" msgstr "Änderungen:" -#: gitk:1968 gitk:1970 +#: gitk:2097 gitk:2099 msgid "Exact" msgstr "Exakt" -#: gitk:1970 gitk:3773 gitk:5518 +#: gitk:2099 gitk:4377 gitk:6178 msgid "IgnCase" msgstr "Kein Groß/Klein" -#: gitk:1970 gitk:3666 gitk:3771 gitk:5514 +#: gitk:2099 gitk:4271 gitk:4375 gitk:6174 msgid "Regexp" msgstr "Regexp" -#: gitk:1972 gitk:1973 gitk:3792 gitk:3822 gitk:3829 gitk:5641 gitk:5708 +#: gitk:2101 gitk:2102 gitk:4396 gitk:4426 gitk:4433 gitk:6302 gitk:6370 msgid "All fields" msgstr "Alle Felder" -#: gitk:1973 gitk:3790 gitk:3822 gitk:5580 +#: gitk:2102 gitk:4394 gitk:4426 gitk:6241 msgid "Headline" msgstr "Ãœberschrift" -#: gitk:1974 gitk:3790 gitk:5580 gitk:5708 gitk:6109 +#: gitk:2103 gitk:4394 gitk:6241 gitk:6370 gitk:6804 msgid "Comments" msgstr "Beschreibung" -#: gitk:1974 gitk:3790 gitk:3794 gitk:3829 gitk:5580 gitk:6045 gitk:7285 -#: gitk:7300 +#: gitk:2103 gitk:4394 gitk:4398 gitk:4433 gitk:6241 gitk:6739 gitk:7991 +#: gitk:8006 msgid "Author" msgstr "Autor" -#: gitk:1974 gitk:3790 gitk:5580 gitk:6047 +#: gitk:2103 gitk:4394 gitk:6241 gitk:6741 msgid "Committer" msgstr "Eintragender" -#: gitk:2003 +#: gitk:2132 msgid "Search" -msgstr "Suche" +msgstr "Suchen" -#: gitk:2010 +#: gitk:2139 msgid "Diff" msgstr "Vergleich" -#: gitk:2012 +#: gitk:2141 msgid "Old version" msgstr "Alte Version" -#: gitk:2014 +#: gitk:2143 msgid "New version" msgstr "Neue Version" -#: gitk:2016 +#: gitk:2145 msgid "Lines of context" msgstr "Kontextzeilen" -#: gitk:2026 +#: gitk:2155 msgid "Ignore space change" msgstr "Leerzeichenänderungen ignorieren" -#: gitk:2084 +#: gitk:2213 msgid "Patch" msgstr "Patch" -#: gitk:2086 +#: gitk:2215 msgid "Tree" msgstr "Baum" -#: gitk:2213 gitk:2226 +#: gitk:2359 gitk:2376 msgid "Diff this -> selected" -msgstr "Vergleich diese -> gewählte" +msgstr "Vergleich: diese -> gewählte" -#: gitk:2214 gitk:2227 +#: gitk:2360 gitk:2377 msgid "Diff selected -> this" -msgstr "Vergleich gewählte -> diese" +msgstr "Vergleich: gewählte -> diese" -#: gitk:2215 gitk:2228 +#: gitk:2361 gitk:2378 msgid "Make patch" msgstr "Patch erstellen" -#: gitk:2216 gitk:7494 +#: gitk:2362 gitk:8369 msgid "Create tag" msgstr "Markierung erstellen" -#: gitk:2217 gitk:7593 +#: gitk:2363 gitk:8475 msgid "Write commit to file" msgstr "Version in Datei schreiben" -#: gitk:2218 gitk:7647 +#: gitk:2364 gitk:8532 msgid "Create new branch" msgstr "Neuen Zweig erstellen" -#: gitk:2219 +#: gitk:2365 msgid "Cherry-pick this commit" msgstr "Diese Version pflücken" -#: gitk:2220 +#: gitk:2366 msgid "Reset HEAD branch to here" msgstr "HEAD-Zweig auf diese Version zurücksetzen" -#: gitk:2234 +#: gitk:2367 +msgid "Mark this commit" +msgstr "Lesezeichen setzen" + +#: gitk:2368 +msgid "Return to mark" +msgstr "Zum Lesezeichen" + +#: gitk:2369 +msgid "Find descendant of this and mark" +msgstr "Abkömmling von Lesezeichen und dieser Version finden" + +#: gitk:2370 +msgid "Compare with marked commit" +msgstr "Mit Lesezeichen vergleichen" + +#: gitk:2384 msgid "Check out this branch" msgstr "Auf diesen Zweig umstellen" -#: gitk:2235 +#: gitk:2385 msgid "Remove this branch" msgstr "Zweig löschen" -#: gitk:2242 +#: gitk:2392 msgid "Highlight this too" msgstr "Diesen auch hervorheben" -#: gitk:2243 +#: gitk:2393 msgid "Highlight this only" msgstr "Nur diesen hervorheben" -#: gitk:2244 +#: gitk:2394 msgid "External diff" -msgstr "Externer Vergleich" +msgstr "Externes Diff-Programm" -#: gitk:2255 +#: gitk:2395 msgid "Blame parent commit" msgstr "Annotieren der Elternversion" -#: gitk:2360 +#: gitk:2402 msgid "Show origin of this line" msgstr "Herkunft dieser Zeile anzeigen" -#: gitk:2361 +#: gitk:2403 msgid "Run git gui blame on this line" -msgstr "Annotieren (»git gui blame«) von dieser Zeile" +msgstr "Diese Zeile annotieren (»git gui blame«)" -#: gitk:2606 +#: gitk:2652 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -317,504 +335,552 @@ msgid "" "Use and redistribute under the terms of the GNU General Public License" msgstr "" "\n" -"Gitk - eine Visualisierung der Git Historie\n" +"Gitk - eine Visualisierung der Git-Historie\n" "\n" "Copyright © 2005-2008 Paul Mackerras\n" "\n" "Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public " "License" -#: gitk:2496 gitk:2557 gitk:7943 +#: gitk:2660 gitk:2722 gitk:8897 msgid "Close" msgstr "Schließen" -#: gitk:2515 +#: gitk:2679 msgid "Gitk key bindings" -msgstr "Gitk Tastaturbelegung" +msgstr "Gitk-Tastaturbelegung" -#: gitk:2517 +#: gitk:2682 msgid "Gitk key bindings:" -msgstr "Gitk Tastaturbelegung:" +msgstr "Gitk-Tastaturbelegung:" -#: gitk:2519 +#: gitk:2684 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tBeenden" -#: gitk:2520 +#: gitk:2685 msgid "<Home>\t\tMove to first commit" msgstr "<Pos1>\t\tZur neuesten Version springen" -#: gitk:2521 +#: gitk:2686 msgid "<End>\t\tMove to last commit" msgstr "<Ende>\t\tZur ältesten Version springen" -#: gitk:2522 +#: gitk:2687 msgid "<Up>, p, i\tMove up one commit" msgstr "<Hoch>, p, i\tNächste neuere Version" -#: gitk:2523 +#: gitk:2688 msgid "<Down>, n, k\tMove down one commit" msgstr "<Runter>, n, k\tNächste ältere Version" -#: gitk:2524 +#: gitk:2689 msgid "<Left>, z, j\tGo back in history list" msgstr "<Links>, z, j\tEine Version zurückgehen" -#: gitk:2525 +#: gitk:2690 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Rechts>, x, l\tEine Version weitergehen" -#: gitk:2526 +#: gitk:2691 msgid "<PageUp>\tMove up one page in commit list" msgstr "<BildHoch>\tEine Seite nach oben blättern" -#: gitk:2527 +#: gitk:2692 msgid "<PageDown>\tMove down one page in commit list" msgstr "<BildRunter>\tEine Seite nach unten blättern" -#: gitk:2528 +#: gitk:2693 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Pos1>\tZum oberen Ende der Versionsliste blättern" -#: gitk:2529 +#: gitk:2694 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-Ende>\tZum unteren Ende der Versionsliste blättern" -#: gitk:2530 +#: gitk:2695 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Hoch>\tVersionsliste eine Zeile nach oben blättern" -#: gitk:2531 +#: gitk:2696 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Runter>\tVersionsliste eine Zeile nach unten blättern" -#: gitk:2532 +#: gitk:2697 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" -msgstr "<%s-BildHoch>\tVersionsliste eine Seite hoch blättern" +msgstr "<%s-BildHoch>\tVersionsliste eine Seite nach oben blättern" -#: gitk:2533 +#: gitk:2698 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-BildRunter>\tVersionsliste eine Seite nach unten blättern" -#: gitk:2534 +#: gitk:2699 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Umschalt-Hoch>\tRückwärts suchen (nach oben; neuere Versionen)" -#: gitk:2535 +#: gitk:2700 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Umschalt-Runter> Suchen (nach unten; ältere Versionen)" -#: gitk:2536 +#: gitk:2701 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Entf>, b\t\tVergleich eine Seite nach oben blättern" -#: gitk:2537 +#: gitk:2702 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Löschtaste>\tVergleich eine Seite nach oben blättern" -#: gitk:2538 +#: gitk:2703 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Leertaste>\tVergleich eine Seite nach unten blättern" -#: gitk:2539 +#: gitk:2704 msgid "u\t\tScroll diff view up 18 lines" -msgstr "u\t\tVergleich um 18 Zeilen nach oben (»up«) blättern" +msgstr "u\t\tVergleich um 18 Zeilen nach oben blättern" -#: gitk:2540 +#: gitk:2705 msgid "d\t\tScroll diff view down 18 lines" -msgstr "d\t\tVergleich um 18 Zeilen nach unten (»down«) blättern" +msgstr "d\t\tVergleich um 18 Zeilen nach unten blättern" -#: gitk:2541 +#: gitk:2706 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSuchen" -#: gitk:2542 +#: gitk:2707 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tWeitersuchen" -#: gitk:2543 +#: gitk:2708 msgid "<Return>\tMove to next find hit" msgstr "<Eingabetaste>\tWeitersuchen" -#: gitk:2544 -msgid "/\t\tMove to next find hit, or redo find" -msgstr "/\t\tWeitersuchen oder neue Suche beginnen" +#: gitk:2709 +msgid "/\t\tFocus the search box" +msgstr "/\t\tTastaturfokus ins Suchfeld" -#: gitk:2545 +#: gitk:2710 msgid "?\t\tMove to previous find hit" msgstr "?\t\tRückwärts weitersuchen" -#: gitk:2546 +#: gitk:2711 msgid "f\t\tScroll diff view to next file" -msgstr "f\t\tVergleich zur nächsten Datei (»file«) blättern" +msgstr "f\t\tVergleich zur nächsten Datei blättern" -#: gitk:2547 +#: gitk:2712 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tWeitersuchen im Vergleich" -#: gitk:2548 +#: gitk:2713 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tRückwärts weitersuchen im Vergleich" -#: gitk:2549 +#: gitk:2714 #, tcl-format msgid "<%s-KP+>\tIncrease font size" -msgstr "<%s-Nummerblock-Plus>\tSchriftgröße vergrößern" +msgstr "<%s-Nummerblock-Plus>\tSchrift vergrößern" -#: gitk:2550 +#: gitk:2715 #, tcl-format msgid "<%s-plus>\tIncrease font size" -msgstr "<%s-Plus>\tSchriftgröße vergrößern" +msgstr "<%s-Plus>\tSchrift vergrößern" -#: gitk:2551 +#: gitk:2716 #, tcl-format msgid "<%s-KP->\tDecrease font size" -msgstr "<%s-Nummernblock-> Schriftgröße verkleinern" +msgstr "<%s-Nummernblock-Minus> Schrift verkleinern" -#: gitk:2552 +#: gitk:2717 #, tcl-format msgid "<%s-minus>\tDecrease font size" -msgstr "<%s-Minus>\tSchriftgröße verkleinern" +msgstr "<%s-Minus>\tSchrift verkleinern" -#: gitk:2553 +#: gitk:2718 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAktualisieren" -#: gitk:2979 +#: gitk:3173 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Fehler beim Holen von »%s« von »%s«:" -#: gitk:3036 gitk:3045 +#: gitk:3230 gitk:3239 #, tcl-format msgid "Error creating temporary directory %s:" -msgstr "Fehler beim Erzeugen eines temporären Verzeichnisses »%s«:" +msgstr "Fehler beim Erzeugen des temporären Verzeichnisses »%s«:" -#: gitk:3058 +#: gitk:3251 msgid "command failed:" msgstr "Kommando fehlgeschlagen:" -#: gitk:3078 +#: gitk:3397 msgid "No such commit" msgstr "Version nicht gefunden" -#: gitk:3083 +#: gitk:3411 msgid "git gui blame: command failed:" msgstr "git gui blame: Kommando fehlgeschlagen:" -#: gitk:3398 +#: gitk:3442 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Zusammenführungs-Spitze konnte nicht gelesen werden: %s" -#: gitk:3406 +#: gitk:3450 #, tcl-format msgid "Error reading index: %s" msgstr "Fehler beim Lesen der Bereitstellung (»index«): %s" -#: gitk:3431 +#: gitk:3475 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "»git blame« konnte nicht gestartet werden: %s" -#: gitk:3434 gitk:6160 +#: gitk:3478 gitk:6209 msgid "Searching" msgstr "Suchen" -#: gitk:3466 +#: gitk:3510 #, tcl-format msgid "Error running git blame: %s" msgstr "Fehler beim Ausführen von »git blame«: %s" -#: gitk:3494 +#: gitk:3538 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "" -"Diese Zeile stammt aus Version %s, welche nicht in dieser Ansicht gezeigt " -"wird." +"Diese Zeile stammt aus Version %s, die nicht in dieser Ansicht gezeigt wird" -#: gitk:3508 +#: gitk:3552 msgid "External diff viewer failed:" -msgstr "Externes Vergleich-(Diff-)Programm fehlgeschlagen:" +msgstr "Externes Diff-Programm fehlgeschlagen:" -#: gitk:3210 +#: gitk:3670 msgid "Gitk view definition" -msgstr "Gitk Ansichten" +msgstr "Gitk-Ansichten" -#: gitk:3630 +#: gitk:3674 msgid "Remember this view" msgstr "Diese Ansicht speichern" -#: gitk:3232 +#: gitk:3675 msgid "Commits to include (arguments to git log):" -msgstr "Versionen anzeigen (Argumente von git-log):" +msgstr "Versionen anzeigen (Argumente von git log):" -#: gitk:3632 +#: gitk:3676 msgid "Use all refs" msgstr "Alle Zweige verwenden" -#: gitk:3633 +#: gitk:3677 msgid "Strictly sort by date" msgstr "Streng nach Datum sortieren" -#: gitk:3634 +#: gitk:3678 msgid "Mark branch sides" msgstr "Zweig-Seiten markieren" -#: gitk:3635 +#: gitk:3679 msgid "Since date:" msgstr "Von Datum:" -#: gitk:3636 +#: gitk:3680 msgid "Until date:" msgstr "Bis Datum:" -#: gitk:3637 +#: gitk:3681 msgid "Max count:" msgstr "Max. Anzahl:" -#: gitk:3638 +#: gitk:3682 msgid "Skip:" msgstr "Ãœberspringen:" -#: gitk:3639 +#: gitk:3683 msgid "Limit to first parent" msgstr "Auf erste Elternversion beschränken" -#: gitk:3640 +#: gitk:3684 msgid "Command to generate more commits to include:" msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:" -#: gitk:3749 +#: gitk:3780 +msgid "Gitk: edit view" +msgstr "Gitk: Ansicht bearbeiten" + +#: gitk:3793 msgid "Name" msgstr "Name" -#: gitk:3797 +#: gitk:3841 msgid "Enter files and directories to include, one per line:" msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):" -#: gitk:3811 +#: gitk:3855 msgid "Apply (F5)" msgstr "Anwenden (F5)" -#: gitk:3849 +#: gitk:3893 msgid "Error in commit selection arguments:" msgstr "Fehler in den ausgewählten Versionen:" -#: gitk:3347 gitk:3399 gitk:3842 gitk:3856 gitk:5060 gitk:10141 gitk:10142 +#: gitk:3946 gitk:3998 gitk:4446 gitk:4460 gitk:5721 gitk:11114 gitk:11115 msgid "None" msgstr "Keine" -#: gitk:3790 gitk:5580 gitk:7287 gitk:7302 +#: gitk:4394 gitk:6241 gitk:7993 gitk:8008 msgid "Date" msgstr "Datum" -#: gitk:3790 gitk:5580 +#: gitk:4394 gitk:6241 msgid "CDate" msgstr "Eintragedatum" -#: gitk:3939 gitk:3944 +#: gitk:4543 gitk:4548 msgid "Descendant" msgstr "Abkömmling" -#: gitk:3940 +#: gitk:4544 msgid "Not descendant" -msgstr "Nicht Abkömmling" +msgstr "Kein Abkömmling" -#: gitk:3947 gitk:3952 +#: gitk:4551 gitk:4556 msgid "Ancestor" msgstr "Vorgänger" -#: gitk:3948 +#: gitk:4552 msgid "Not ancestor" -msgstr "Nicht Vorgänger" +msgstr "Kein Vorgänger" -#: gitk:4187 +#: gitk:4842 msgid "Local changes checked in to index but not committed" msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen" -#: gitk:4220 +#: gitk:4878 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokale Änderungen, nicht bereitgestellt" -#: gitk:6673 +#: gitk:6559 +msgid "many" +msgstr "viele" + +#: gitk:6743 msgid "Tags:" msgstr "Markierungen:" -#: gitk:6066 gitk:6072 gitk:7280 +#: gitk:6760 gitk:6766 gitk:7986 msgid "Parent" msgstr "Eltern" -#: gitk:6077 +#: gitk:6771 msgid "Child" msgstr "Kind" -#: gitk:6086 +#: gitk:6780 msgid "Branch" msgstr "Zweig" -#: gitk:6089 +#: gitk:6783 msgid "Follows" msgstr "Folgt auf" -#: gitk:6092 +#: gitk:6786 msgid "Precedes" msgstr "Vorgänger von" -#: gitk:7209 +#: gitk:7279 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fehler beim Laden des Vergleichs: %s" -#: gitk:7748 +#: gitk:7819 msgid "Goto:" msgstr "Gehe zu:" -#: gitk:7115 +#: gitk:7821 msgid "SHA1 ID:" msgstr "SHA1-Hashwert:" -#: gitk:7134 +#: gitk:7840 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Kurzer SHA1-Hashwert »%s« ist mehrdeutig" -#: gitk:7146 +#: gitk:7852 #, tcl-format msgid "SHA1 id %s is not known" -msgstr "SHA1-Hashwert »%s« unbekannt" +msgstr "SHA1-Hashwert »%s« ist unbekannt" -#: gitk:7148 +#: gitk:7854 #, tcl-format msgid "Tag/Head %s is not known" msgstr "Markierung/Zweig »%s« ist unbekannt" -#: gitk:7290 +#: gitk:7996 msgid "Children" msgstr "Kinder" -#: gitk:7347 +#: gitk:8053 #, tcl-format msgid "Reset %s branch to here" msgstr "Zweig »%s« hierher zurücksetzen" -#: gitk:7349 +#: gitk:8055 msgid "Detached head: can't reset" msgstr "Zweigspitze ist abgetrennt: Zurücksetzen nicht möglich" -#: gitk:7381 +#: gitk:8164 gitk:8170 +msgid "Skipping merge commit " +msgstr "Ãœberspringe Zusammenführungs-Version " + +#: gitk:8179 gitk:8184 +msgid "Error getting patch ID for " +msgstr "Fehler beim Holen der Patch-ID für " + +#: gitk:8180 gitk:8185 +msgid " - stopping\n" +msgstr " - Abbruch.\n" + +#: gitk:8190 gitk:8193 gitk:8201 gitk:8211 gitk:8220 +msgid "Commit " +msgstr "Version " + +#: gitk:8194 +msgid "" +" is the same patch as\n" +" " +msgstr "" +" ist das gleiche Patch wie\n" +" " + +#: gitk:8202 +msgid "" +" differs from\n" +" " +msgstr "" +" ist unterschiedlich von\n" +" " + +#: gitk:8204 +msgid "- stopping\n" +msgstr "- Abbruch.\n" + +#: gitk:8212 gitk:8221 +#, tcl-format +msgid " has %s children - stopping\n" +msgstr " hat %s Kinder. Abbruch\n" + +#: gitk:8252 msgid "Top" msgstr "Oben" -#: gitk:7382 +#: gitk:8253 msgid "From" msgstr "Von" -#: gitk:7387 +#: gitk:8258 msgid "To" msgstr "bis" -#: gitk:7410 +#: gitk:8282 msgid "Generate patch" msgstr "Patch erstellen" -#: gitk:7412 +#: gitk:8284 msgid "From:" msgstr "Von:" -#: gitk:7421 +#: gitk:8293 msgid "To:" msgstr "bis:" -#: gitk:7430 +#: gitk:8302 msgid "Reverse" msgstr "Umgekehrt" -#: gitk:7432 gitk:7607 +#: gitk:8304 gitk:8489 msgid "Output file:" msgstr "Ausgabedatei:" -#: gitk:7438 +#: gitk:8310 msgid "Generate" msgstr "Erzeugen" -#: gitk:7474 +#: gitk:8348 msgid "Error creating patch:" -msgstr "Fehler beim Patch erzeugen:" +msgstr "Fehler beim Erzeugen des Patches:" -#: gitk:7496 gitk:7595 gitk:7649 +#: gitk:8371 gitk:8477 gitk:8534 msgid "ID:" msgstr "ID:" -#: gitk:7505 +#: gitk:8380 msgid "Tag name:" msgstr "Markierungsname:" -#: gitk:7509 gitk:7659 +#: gitk:8384 gitk:8543 msgid "Create" msgstr "Erstellen" -#: gitk:7524 +#: gitk:8401 msgid "No tag name specified" msgstr "Kein Markierungsname angegeben" -#: gitk:7528 +#: gitk:8405 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Markierung »%s« existiert bereits." -#: gitk:7534 +#: gitk:8411 msgid "Error creating tag:" -msgstr "Fehler bei Markierung erstellen:" +msgstr "Fehler beim Erstellen der Markierung:" -#: gitk:7604 +#: gitk:8486 msgid "Command:" msgstr "Kommando:" -#: gitk:7612 +#: gitk:8494 msgid "Write" msgstr "Schreiben" -#: gitk:7628 +#: gitk:8512 msgid "Error writing commit:" msgstr "Fehler beim Schreiben der Version:" -#: gitk:7654 +#: gitk:8539 msgid "Name:" msgstr "Name:" -#: gitk:7674 +#: gitk:8562 msgid "Please specify a name for the new branch" msgstr "Bitte geben Sie einen Namen für den neuen Zweig an." -#: gitk:8328 +#: gitk:8567 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Zweig »%s« existiert bereits. Soll er überschrieben werden?" -#: gitk:8394 +#: gitk:8633 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut " "eintragen?" -#: gitk:7718 +#: gitk:8638 msgid "Cherry-picking" msgstr "Version pflücken" -#: gitk:8408 +#: gitk:8647 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -822,45 +888,45 @@ msgid "" msgstr "" "Pflücken fehlgeschlagen, da noch lokale Änderungen in Datei »%s«\n" "vorliegen. Bitte diese Änderungen eintragen, zurücksetzen oder\n" -"zwischenspeichern (»git stash») und dann erneut versuchen." +"zwischenspeichern (»git stash«) und dann erneut versuchen." -#: gitk:8414 +#: gitk:8653 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" msgstr "" "Pflücken fehlgeschlagen, da ein Zusammenführungs-Konflikt aufgetreten\n" -"ist. Soll das »git citool« (Zusammenführungs-Werkzeug) aufgerufen\n" +"ist. Soll das Zusammenführungs-Werkzeug (»git citool«) aufgerufen\n" "werden, um diesen Konflikt aufzulösen?" -#: gitk:8430 +#: gitk:8669 msgid "No changes committed" msgstr "Keine Änderungen eingetragen" -#: gitk:7745 +#: gitk:8695 msgid "Confirm reset" msgstr "Zurücksetzen bestätigen" -#: gitk:7747 +#: gitk:8697 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Zweig »%s« auf »%s« zurücksetzen?" -#: gitk:7751 +#: gitk:8701 msgid "Reset type:" msgstr "Art des Zurücksetzens:" -#: gitk:7755 +#: gitk:8705 msgid "Soft: Leave working tree and index untouched" msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert" -#: gitk:7758 +#: gitk:8708 msgid "Mixed: Leave working tree untouched, reset index" msgstr "" "Gemischt: Arbeitskopie unverändert,\n" "Bereitstellung zurückgesetzt" -#: gitk:7761 +#: gitk:8711 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -868,21 +934,21 @@ msgstr "" "Hart: Arbeitskopie und Bereitstellung\n" "(Alle lokalen Änderungen werden gelöscht)" -#: gitk:7777 +#: gitk:8728 msgid "Resetting" msgstr "Zurücksetzen" -#: gitk:7834 +#: gitk:8785 msgid "Checking out" msgstr "Umstellen" -#: gitk:7885 +#: gitk:8838 msgid "Cannot delete the currently checked-out branch" msgstr "" "Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht " "gelöscht werden." -#: gitk:7891 +#: gitk:8844 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -891,174 +957,174 @@ msgstr "" "Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n" "Zweig »%s« trotzdem löschen?" -#: gitk:7922 +#: gitk:8875 #, tcl-format msgid "Tags and heads: %s" msgstr "Markierungen und Zweige: %s" -#: gitk:7936 +#: gitk:8890 msgid "Filter" msgstr "Filtern" -#: gitk:8230 +#: gitk:9185 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." msgstr "" -"Fehler beim Lesen der Strukturinformationen; Zweige und Vorgänger/Nachfolger " -"Informationen werden unvollständig sein." +"Fehler beim Lesen der Strukturinformationen; Zweige und Informationen zu " +"Vorgänger/Nachfolger werden unvollständig sein." -#: gitk:9216 +#: gitk:10171 msgid "Tag" msgstr "Markierung" -#: gitk:9216 +#: gitk:10171 msgid "Id" msgstr "Id" -#: gitk:9262 +#: gitk:10219 msgid "Gitk font chooser" -msgstr "Gitk Schriften wählen" +msgstr "Gitk-Schriften wählen" -#: gitk:9279 +#: gitk:10236 msgid "B" msgstr "F" -#: gitk:9282 +#: gitk:10239 msgid "I" msgstr "K" -#: gitk:9375 +#: gitk:10334 msgid "Gitk preferences" -msgstr "Gitk Einstellungen" +msgstr "Gitk-Einstellungen" -#: gitk:9376 +#: gitk:10336 msgid "Commit list display options" -msgstr "Anzeige Versionsliste" +msgstr "Anzeige der Versionsliste" -#: gitk:9379 +#: gitk:10339 msgid "Maximum graph width (lines)" msgstr "Maximale Graphenbreite (Zeilen)" -#: gitk:9383 +#: gitk:10343 #, tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximale Graphenbreite (% des Fensters)" -#: gitk:9388 +#: gitk:10347 msgid "Show local changes" msgstr "Lokale Änderungen anzeigen" -#: gitk:9393 +#: gitk:10350 msgid "Auto-select SHA1" -msgstr "SHA1-Hashwert automatisch markieren" +msgstr "SHA1-Hashwert automatisch auswählen" -#: gitk:9398 +#: gitk:10354 msgid "Diff display options" -msgstr "Anzeige Vergleich" +msgstr "Anzeige des Vergleichs" -#: gitk:9400 +#: gitk:10356 msgid "Tab spacing" msgstr "Tabulatorbreite" -#: gitk:9404 +#: gitk:10359 msgid "Display nearby tags" -msgstr "Naheliegende Ãœberschriften anzeigen" +msgstr "Naheliegende Markierungen anzeigen" -#: gitk:9409 +#: gitk:10362 msgid "Limit diffs to listed paths" msgstr "Vergleich nur für angezeigte Pfade" -#: gitk:9414 +#: gitk:10365 msgid "Support per-file encodings" msgstr "Zeichenkodierung pro Datei ermitteln" -#: gitk:9421 +#: gitk:10371 gitk:10436 msgid "External diff tool" -msgstr "Externes Vergleich-(Diff-)Programm" +msgstr "Externes Diff-Programm" -#: gitk:9423 +#: gitk:10373 msgid "Choose..." -msgstr "Wählen..." +msgstr "Wählen ..." -#: gitk:9428 +#: gitk:10378 msgid "Colors: press to choose" msgstr "Farben: Klicken zum Wählen" -#: gitk:9431 +#: gitk:10381 msgid "Background" msgstr "Hintergrund" -#: gitk:10153 gitk:10183 +#: gitk:10382 gitk:10412 msgid "background" msgstr "Hintergrund" -#: gitk:10156 +#: gitk:10385 msgid "Foreground" msgstr "Vordergrund" -#: gitk:10157 +#: gitk:10386 msgid "foreground" msgstr "Vordergrund" -#: gitk:10160 +#: gitk:10389 msgid "Diff: old lines" msgstr "Vergleich: Alte Zeilen" -#: gitk:10161 +#: gitk:10390 msgid "diff old lines" msgstr "Vergleich - Alte Zeilen" -#: gitk:10165 +#: gitk:10394 msgid "Diff: new lines" msgstr "Vergleich: Neue Zeilen" -#: gitk:10166 +#: gitk:10395 msgid "diff new lines" msgstr "Vergleich - Neue Zeilen" -#: gitk:10170 +#: gitk:10399 msgid "Diff: hunk header" msgstr "Vergleich: Änderungstitel" -#: gitk:10172 +#: gitk:10401 msgid "diff hunk header" msgstr "Vergleich - Änderungstitel" -#: gitk:10176 +#: gitk:10405 msgid "Marked line bg" -msgstr "Markierte Zeile Hintergrund" +msgstr "Hintergrund für markierte Zeile" -#: gitk:10178 +#: gitk:10407 msgid "marked line background" -msgstr "markierte Zeile Hintergrund" +msgstr "Hintergrund für markierte Zeile" -#: gitk:10182 +#: gitk:10411 msgid "Select bg" -msgstr "Hintergrundfarbe Auswählen" +msgstr "Hintergrundfarbe auswählen" -#: gitk:9459 +#: gitk:10415 msgid "Fonts: press to choose" msgstr "Schriftart: Klicken zum Wählen" -#: gitk:9461 +#: gitk:10417 msgid "Main font" msgstr "Programmschriftart" -#: gitk:9462 +#: gitk:10418 msgid "Diff display font" -msgstr "Vergleich" +msgstr "Schriftart für Vergleich" -#: gitk:9463 +#: gitk:10419 msgid "User interface font" msgstr "Beschriftungen" -#: gitk:9488 +#: gitk:10446 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: Farbe wählen für %s" -#: gitk:9934 +#: gitk:10893 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1066,24 +1132,24 @@ msgstr "" "Gitk läuft nicht mit dieser Version von Tcl/Tk.\n" "Gitk benötigt mindestens Tcl/Tk 8.4." -#: gitk:10047 +#: gitk:11020 msgid "Cannot find a git repository here." msgstr "Kein Git-Projektarchiv gefunden." -#: gitk:10051 +#: gitk:11024 #, tcl-format msgid "Cannot find the git directory \"%s\"." msgstr "Git-Verzeichnis »%s« wurde nicht gefunden." -#: gitk:10098 +#: gitk:11071 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert." -#: gitk:10110 +#: gitk:11083 msgid "Bad arguments to gitk:" msgstr "Falsche Kommandozeilen-Parameter für gitk:" -#: gitk:10170 +#: gitk:11167 msgid "Command line" msgstr "Kommandozeile" diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po new file mode 100644 index 0000000000..704eba8f9d --- /dev/null +++ b/gitk-git/po/ru.po @@ -0,0 +1,1085 @@ +# +# Translation of gitk to Russian. +# +msgid "" +msgstr "" +"Project-Id-Version: gitk\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2009-04-24 16:00+0200\n" +"PO-Revision-Date: 2009-04-24 16:00+0200\n" +"Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n" +"Language-Team: Russian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: gitk:113 +msgid "Couldn't get list of unmerged files:" +msgstr "" +"Ðевозможно получить ÑпиÑок файлов незавершённой операции ÑлиÑниÑ:" + +#: gitk:268 +msgid "Error parsing revisions:" +msgstr "Ошибка в идентификаторе верÑии:" + +#: gitk:323 +msgid "Error executing --argscmd command:" +msgstr "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ‹ заданой --argscmd:" + +#: gitk:336 +msgid "No files selected: --merge specified but no files are unmerged." +msgstr "" +"Файлы не выбраны: указан --merge, но не было найдено ни одного файла " +"где Ñта Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° быть завершена." + +#: gitk:339 +msgid "" +"No files selected: --merge specified but no unmerged files are within file " +"limit." +msgstr "" +"Файлы не выбраны: указан --merge, но в рамках указаного " +"Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð½Ð° имена файлов нет ни одного " +"где Ñта Ð¾Ð¿ÐµÑ€Ð°Ñ†Ð¸Ñ Ð´Ð¾Ð»Ð¶Ð½Ð° быть завершена." + +#: gitk:361 gitk:508 +msgid "Error executing git log:" +msgstr "Ошибка запуÑка git log:" + +#: gitk:379 +msgid "Reading" +msgstr "Чтение" + +#: gitk:439 gitk:4021 +msgid "Reading commits..." +msgstr "Чтение верÑий..." + +#: gitk:442 gitk:1560 gitk:4024 +msgid "No commits selected" +msgstr "Ðичего не выбрано" + +#: gitk:1436 +msgid "Can't parse git log output:" +msgstr "Ошибка обработки вывода команды git log:" + +#: gitk:1656 +msgid "No commit information available" +msgstr "Ðет информации о ÑоÑтоÑнии" + +#: gitk:1791 gitk:1815 gitk:3814 gitk:8478 gitk:10014 gitk:10186 +msgid "OK" +msgstr "Ok" + +#: gitk:1817 gitk:3816 gitk:8078 gitk:8152 gitk:8259 gitk:8308 gitk:8480 +#: gitk:10015 gitk:10187 +msgid "Cancel" +msgstr "Отмена" + +#: gitk:1915 +msgid "Update" +msgstr "Обновить" + +#: gitk:1916 +msgid "Reload" +msgstr "Перечитать" + +#: gitk:1917 +msgid "Reread references" +msgstr "Обновить ÑпиÑок ÑÑылок" + +#: gitk:1918 +msgid "List references" +msgstr "СпиÑок ÑÑылок" + +#: gitk:1920 +msgid "Start git gui" +msgstr "ЗапуÑтить git gui" + +#: gitk:1922 +msgid "Quit" +msgstr "Завершить" + +#: gitk:1914 +msgid "File" +msgstr "Файл" + +#: gitk:1925 +msgid "Preferences" +msgstr "ÐаÑтройки" + +#: gitk:1924 +msgid "Edit" +msgstr "Редактировать" + +#: gitk:1928 +msgid "New view..." +msgstr "Ðовое предÑтавление..." + +#: gitk:1929 +msgid "Edit view..." +msgstr "Редактировать предÑтавление..." + +#: gitk:1930 +msgid "Delete view" +msgstr "Удалить предÑтавление" + +#: gitk:1932 +msgid "All files" +msgstr "Ð’Ñе файлы" + +#: gitk:1927 gitk:3626 +msgid "View" +msgstr "ПредÑтавление" + +#: gitk:1935 gitk:2609 +msgid "About gitk" +msgstr "О gitk" + +#: gitk:1936 +msgid "Key bindings" +msgstr "ÐÐ°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹" + +#: gitk:1934 +msgid "Help" +msgstr "ПодÑказка" + +#: gitk:1994 +msgid "SHA1 ID: " +msgstr "SHA1:" + +#: gitk:2025 +msgid "Row" +msgstr "Строка" + +#: gitk:2056 +msgid "Find" +msgstr "ПоиÑк" + +#: gitk:2057 +msgid "next" +msgstr "След." + +#: gitk:2058 +msgid "prev" +msgstr "Пред." + +#: gitk:2059 +msgid "commit" +msgstr "ÑоÑтоÑние" + +#: gitk:2062 gitk:2064 gitk:4179 gitk:4202 gitk:4226 gitk:6164 gitk:6236 +#: gitk:6320 +msgid "containing:" +msgstr "Ñодержащее:" + +#: gitk:2065 gitk:3117 gitk:3122 gitk:4254 +msgid "touching paths:" +msgstr "каÑательно файлов:" + +#: gitk:2066 gitk:4259 +msgid "adding/removing string:" +msgstr "добавив/удалив Ñтроку:" + +#: gitk:2075 gitk:2077 +msgid "Exact" +msgstr "Точно" + +#: gitk:2077 gitk:4334 gitk:6132 +msgid "IgnCase" +msgstr "Игнорировать большие/маленькие" + +#: gitk:2077 gitk:4228 gitk:4332 gitk:6128 +msgid "Regexp" +msgstr "РегулÑрные выражениÑ" + +#: gitk:2079 gitk:2080 gitk:4353 gitk:4383 gitk:4390 gitk:6256 gitk:6324 +msgid "All fields" +msgstr "Во вÑех полÑÑ…" + +#: gitk:2080 gitk:4351 gitk:4383 gitk:6195 +msgid "Headline" +msgstr "Заголовок" + +#: gitk:2081 gitk:4351 gitk:6195 gitk:6324 gitk:6737 +msgid "Comments" +msgstr "Комментарии" + +#: gitk:2081 gitk:4351 gitk:4355 gitk:4390 gitk:6195 gitk:6672 gitk:7923 +#: gitk:7938 +msgid "Author" +msgstr "Ðвтор" + +#: gitk:2081 gitk:4351 gitk:6195 gitk:6674 +msgid "Committer" +msgstr "Сохранивший ÑоÑтоÑние" + +#: gitk:2110 +msgid "Search" +msgstr "Ðайти" + +#: gitk:2117 +msgid "Diff" +msgstr "Сравнить" + +#: gitk:2119 +msgid "Old version" +msgstr "Ð¡Ñ‚Ð°Ñ€Ð°Ñ Ð²ÐµÑ€ÑиÑ" + +#: gitk:2121 +msgid "New version" +msgstr "ÐÐ¾Ð²Ð°Ñ Ð²ÐµÑ€ÑиÑ" + +#: gitk:2123 +msgid "Lines of context" +msgstr "Строк контекÑта" + +#: gitk:2133 +msgid "Ignore space change" +msgstr "Игнорировать пробелы" + +#: gitk:2191 +msgid "Patch" +msgstr "Патч" + +#: gitk:2193 +msgid "Tree" +msgstr "Файлы" + +#: gitk:2326 gitk:2339 +msgid "Diff this -> selected" +msgstr "Сравнить Ñто ÑоÑтоÑние Ñ Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ñ‹Ð¼" + +#: gitk:2327 gitk:2340 +msgid "Diff selected -> this" +msgstr "Сравнить выделеное Ñ Ñтим ÑоÑтоÑнием" + +#: gitk:2328 gitk:2341 +msgid "Make patch" +msgstr "Создать патч" + +#: gitk:2329 gitk:8136 +msgid "Create tag" +msgstr "Создать метку" + +#: gitk:2330 gitk:8239 +msgid "Write commit to file" +msgstr "Сохранить Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² файл" + +#: gitk:2331 gitk:8296 +msgid "Create new branch" +msgstr "Создать ветвь" + +#: gitk:2332 +msgid "Cherry-pick this commit" +msgstr "Скопировать Ñто ÑоÑтоÑние" + +#: gitk:2333 +msgid "Reset HEAD branch to here" +msgstr "УÑтановить HEAD на Ñто ÑоÑтоÑние" + +#: gitk:2347 +msgid "Check out this branch" +msgstr "Перейти на Ñту ветвь" + +#: gitk:2348 +msgid "Remove this branch" +msgstr "Удалить Ñту ветвь" + +#: gitk:2355 +msgid "Highlight this too" +msgstr "ПодÑветить Ñтот тоже" + +#: gitk:2356 +msgid "Highlight this only" +msgstr "ПодÑветить только Ñтот" + +#: gitk:2357 +msgid "External diff" +msgstr "Программа ÑравнениÑ" + +#: gitk:2358 +msgid "Blame parent commit" +msgstr "Ðннотировать родительÑкое ÑоÑтоÑние" + +#: gitk:2365 +msgid "Show origin of this line" +msgstr "Показать иÑточник Ñтой Ñтроки" + +#: gitk:2366 +msgid "Run git gui blame on this line" +msgstr "ЗапуÑтить git gui blame Ð´Ð»Ñ Ñтой Ñтроки" + +#: gitk:2611 +msgid "" +"\n" +"Gitk - a commit viewer for git\n" +"\n" +"Copyright © 2005-2008 Paul Mackerras\n" +"\n" +"Use and redistribute under the terms of the GNU General Public License" +msgstr "" +"\n" +"Gitk - программа проÑмотра иÑтории репозиториев Git\n" +"\n" +"Copyright (c) 2005-2008 Paul Mackerras\n" +"\n" +"ИÑпользование и раÑпроÑтранение ÑоглаÑно уÑловиÑм GNU General Public License" + +#: gitk:2619 gitk:2681 gitk:8661 +msgid "Close" +msgstr "Закрыть" + +#: gitk:2638 +msgid "Gitk key bindings" +msgstr "ÐÐ°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹ в Gitk" + +#: gitk:2641 +msgid "Gitk key bindings:" +msgstr "ÐÐ°Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ ÐºÐ»Ð°Ð²Ð¸Ð°Ñ‚ÑƒÑ€Ñ‹ в Gitk:" + +#: gitk:2643 +#, tcl-format +msgid "<%s-Q>\t\tQuit" +msgstr "<%s-Q>\t\tЗавершить" + +#: gitk:2644 +msgid "<Home>\t\tMove to first commit" +msgstr "<Home>\t\tПерейти к первому ÑоÑтоÑнию" + +#: gitk:2645 +msgid "<End>\t\tMove to last commit" +msgstr "<End>\t\tПерейти к поÑледнему ÑоÑтоÑнию" + +#: gitk:2646 +msgid "<Up>, p, i\tMove up one commit" +msgstr "<Up>, p, i\tПерейти к Ñледующему ÑоÑтоÑнию" + +#: gitk:2647 +msgid "<Down>, n, k\tMove down one commit" +msgstr "<Down>, n, k\tПерейти к предыдущему ÑоÑтоÑнию" + +#: gitk:2648 +msgid "<Left>, z, j\tGo back in history list" +msgstr "<Left>, z, j\tПоказать ранее поÑещённое ÑоÑтоÑние" + +#: gitk:2649 +msgid "<Right>, x, l\tGo forward in history list" +msgstr "<Right>, x, l\tПоказать Ñледующее поÑещённое ÑоÑтоÑние" + +#: gitk:2650 +msgid "<PageUp>\tMove up one page in commit list" +msgstr "<PageUp>\tПерейти на Ñтраницу выше в ÑпиÑке ÑоÑтоÑний" + +#: gitk:2651 +msgid "<PageDown>\tMove down one page in commit list" +msgstr "<PageDown>\tПерейти на Ñтраницу ниже в ÑпиÑке ÑоÑтоÑний" + +#: gitk:2652 +#, tcl-format +msgid "<%s-Home>\tScroll to top of commit list" +msgstr "<%s-Home>\tПоказать начало ÑпиÑка ÑоÑтоÑний" + +#: gitk:2653 +#, tcl-format +msgid "<%s-End>\tScroll to bottom of commit list" +msgstr "<%s-End>\tПоказать конец ÑпиÑка ÑоÑтоÑний" + +#: gitk:2654 +#, tcl-format +msgid "<%s-Up>\tScroll commit list up one line" +msgstr "<%s-Up>\tПровернуть ÑпиÑок ÑоÑтоÑний вверх" + +#: gitk:2655 +#, tcl-format +msgid "<%s-Down>\tScroll commit list down one line" +msgstr "<%s-Down>\tПровернуть ÑпиÑок ÑоÑтоÑний вниз" + +#: gitk:2656 +#, tcl-format +msgid "<%s-PageUp>\tScroll commit list up one page" +msgstr "<%s-PageUp>\tПровернуть ÑпиÑок ÑоÑтоÑний на Ñтраницу вверх" + +#: gitk:2657 +#, tcl-format +msgid "<%s-PageDown>\tScroll commit list down one page" +msgstr "<%s-PageDown>\tПровернуть ÑпиÑок ÑоÑтоÑний на Ñтраницу вниз" + +#: gitk:2658 +msgid "<Shift-Up>\tFind backwards (upwards, later commits)" +msgstr "" +"<Shift-Up>\tПоиÑк в обратном порÑдке (вверх, Ñреди новых ÑоÑтоÑний)" + +#: gitk:2659 +msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" +msgstr "<Shift-Down>\tПоиÑк (вниз, Ñреди Ñтарых ÑоÑтоÑний)" + +#: gitk:2660 +msgid "<Delete>, b\tScroll diff view up one page" +msgstr "<Delete>, b\tПрокрутить ÑпиÑок изменений на Ñтраницу выше" + +#: gitk:2661 +msgid "<Backspace>\tScroll diff view up one page" +msgstr "<Backspace>\tПрокрутить ÑпиÑок изменений на Ñтраницу выше" + +#: gitk:2662 +msgid "<Space>\t\tScroll diff view down one page" +msgstr "<Leertaste>\t\tПрокрутить ÑпиÑок изменений на Ñтраницу ниже" + +#: gitk:2663 +msgid "u\t\tScroll diff view up 18 lines" +msgstr "u\t\tПрокрутить ÑпиÑок изменений на 18 Ñтрок вверх" + +#: gitk:2664 +msgid "d\t\tScroll diff view down 18 lines" +msgstr "d\t\tПрокрутить ÑпиÑок изменений на 18 Ñтрок вниз" + +#: gitk:2665 +#, tcl-format +msgid "<%s-F>\t\tFind" +msgstr "<%s-F>\t\tПоиÑк" + +#: gitk:2666 +#, tcl-format +msgid "<%s-G>\t\tMove to next find hit" +msgstr "<%s-G>\t\tПерейти к Ñледующему найденому ÑоÑтоÑнию" + +#: gitk:2667 +msgid "<Return>\tMove to next find hit" +msgstr "<Return>\tПерейти к Ñледующему найденому ÑоÑтоÑнию" + +#: gitk:2668 +msgid "/\t\tFocus the search box" +msgstr "/\t\tПерейти к полю поиÑка" + +#: gitk:2669 +msgid "?\t\tMove to previous find hit" +msgstr "?\t\tПерейти к предыдущему найденому ÑоÑтоÑнию" + +#: gitk:2670 +msgid "f\t\tScroll diff view to next file" +msgstr "f\t\tПрокрутить ÑпиÑок изменений к Ñледующему файлу" + +#: gitk:2671 +#, tcl-format +msgid "<%s-S>\t\tSearch for next hit in diff view" +msgstr "<%s-S>\t\tПродолжить поиÑк в ÑпиÑке изменений" + +#: gitk:2672 +#, tcl-format +msgid "<%s-R>\t\tSearch for previous hit in diff view" +msgstr "<%s-R>\t\tПерейти к предыдущему найденому текÑту в ÑпиÑке изменений" + +#: gitk:2673 +#, tcl-format +msgid "<%s-KP+>\tIncrease font size" +msgstr "<%s-KP+>\tУвеличить размер шрифта" + +#: gitk:2674 +#, tcl-format +msgid "<%s-plus>\tIncrease font size" +msgstr "<%s-plus>\tУвеличить размер шрифта" + +#: gitk:2675 +#, tcl-format +msgid "<%s-KP->\tDecrease font size" +msgstr "<%s-KP->\tУменьшить размер шрифта" + +#: gitk:2676 +#, tcl-format +msgid "<%s-minus>\tDecrease font size" +msgstr "<%s-minus>\tУменьшить размер шрифта" + +#: gitk:2677 +msgid "<F5>\t\tUpdate" +msgstr "<F5>\t\tОбновить" + +#: gitk:3132 +#, tcl-format +msgid "Error getting \"%s\" from %s:" +msgstr "Ошибка Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ \"%s\" из %s:" + +#: gitk:3189 gitk:3198 +#, tcl-format +msgid "Error creating temporary directory %s:" +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð¾Ð³Ð¾ каталога %s:" + +#: gitk:3211 +msgid "command failed:" +msgstr "ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ‹:" + +#: gitk:3357 +msgid "No such commit" +msgstr "СоÑтоÑние не найдено" + +#: gitk:3371 +msgid "git gui blame: command failed:" +msgstr "git gui blame: ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ñ‹:" + +#: gitk:3402 +#, tcl-format +msgid "Couldn't read merge head: %s" +msgstr "Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ MERGE_HEAD: %s" + +#: gitk:3410 +#, tcl-format +msgid "Error reading index: %s" +msgstr "Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¸Ð½Ð´ÐµÐºÑа: %s" + +#: gitk:3435 +#, tcl-format +msgid "Couldn't start git blame: %s" +msgstr "Ошибка запуÑка git blame: %s" + +#: gitk:3438 gitk:6163 +msgid "Searching" +msgstr "ПоиÑк" + +#: gitk:3470 +#, tcl-format +msgid "Error running git blame: %s" +msgstr "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ git blame: %s" + +#: gitk:3498 +#, tcl-format +msgid "That line comes from commit %s, which is not in this view" +msgstr "" +"Ðта Ñтрока принадлежит ÑоÑтоÑнию %s, которое не показано в Ñтом " +"предÑтавлении" + +#: gitk:3512 +msgid "External diff viewer failed:" +msgstr "Ошибка Ð²Ñ‹Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñ‹ ÑравнениÑ:" + +#: gitk:3630 +msgid "Gitk view definition" +msgstr "Gitk определение предÑтавлений" + +#: gitk:3634 +msgid "Remember this view" +msgstr "Запомнить предÑтавление" + +#: gitk:3635 +msgid "Commits to include (arguments to git log):" +msgstr "Включить ÑоÑтоÑÐ½Ð¸Ñ (аргументы Ð´Ð»Ñ git-log):" + +#: gitk:3636 +msgid "Use all refs" +msgstr "ИÑпользовать вÑе ветви" + +#: gitk:3637 +msgid "Strictly sort by date" +msgstr "Ð¡Ñ‚Ñ€Ð¾Ð³Ð°Ñ Ñортировка по дате" + +#: gitk:3638 +msgid "Mark branch sides" +msgstr "Отметить Ñтороны ветвей" + +#: gitk:3639 +msgid "Since date:" +msgstr "С даты:" + +#: gitk:3640 +msgid "Until date:" +msgstr "По дату:" + +#: gitk:3641 +msgid "Max count:" +msgstr "МакÑ. количеÑтво:" + +#: gitk:3642 +msgid "Skip:" +msgstr "ПропуÑтить:" + +#: gitk:3643 +msgid "Limit to first parent" +msgstr "Ограничить первым предком" + +#: gitk:3644 +msgid "Command to generate more commits to include:" +msgstr "Ð”Ð¾Ð¿Ð¾Ð»Ð½Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° Ð´Ð»Ñ ÑпиÑка ÑоÑтоÑний:" + +#: gitk:3753 +msgid "Name" +msgstr "ИмÑ" + +#: gitk:3801 +msgid "Enter files and directories to include, one per line:" +msgstr "Файлы и каталоги Ð´Ð»Ñ Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð¸Ñтории, по одному на Ñтроку:" + +#: gitk:3815 +msgid "Apply (F5)" +msgstr "Применить (F5)" + +#: gitk:3853 +msgid "Error in commit selection arguments:" +msgstr "Ошибка в параметрах выбора ÑоÑтоÑний:" + +#: gitk:3906 gitk:3958 gitk:4403 gitk:4417 gitk:5675 gitk:10867 gitk:10868 +msgid "None" +msgstr "Ðи одного" + +#: gitk:4351 gitk:6195 gitk:7925 gitk:7940 +msgid "Date" +msgstr "Дата" + +#: gitk:4351 gitk:6195 +msgid "CDate" +msgstr "Дата ввода" + +#: gitk:4500 gitk:4505 +msgid "Descendant" +msgstr "Порождённое" + +#: gitk:4501 +msgid "Not descendant" +msgstr "Ðе порождённое" + +#: gitk:4508 gitk:4513 +msgid "Ancestor" +msgstr "Предок" + +#: gitk:4509 +msgid "Not ancestor" +msgstr "Ðе предок" + +#: gitk:4799 +msgid "Local changes checked in to index but not committed" +msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð·Ð°Ñ€ÐµÐ³Ð¸Ñтрированные в индекÑе, но не Ñохранённые" + +#: gitk:4835 +msgid "Local uncommitted changes, not checked in to index" +msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² рабочем каталоге, не зарегиÑтрированные в индекÑе" + +#: gitk:6676 +msgid "Tags:" +msgstr "Таги:" + +#: gitk:6693 gitk:6699 gitk:7918 +msgid "Parent" +msgstr "Предок" + +#: gitk:6704 +msgid "Child" +msgstr "Потомок" + +#: gitk:6713 +msgid "Branch" +msgstr "Ветвь" + +#: gitk:6716 +msgid "Follows" +msgstr "Следует за" + +#: gitk:6719 +msgid "Precedes" +msgstr "ПредшеÑтвует" + +#: gitk:7212 +#, tcl-format +msgid "Error getting diffs: %s" +msgstr "Ошибка Ð¿Ð¾Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ð¹: %s" + +#: gitk:7751 +msgid "Goto:" +msgstr "Перейти к:" + +#: gitk:7753 +msgid "SHA1 ID:" +msgstr "SHA1 ID:" + +#: gitk:7772 +#, tcl-format +msgid "Short SHA1 id %s is ambiguous" +msgstr "Сокращённый SHA1 идентификатор %s неоднозначен" + +#: gitk:7784 +#, tcl-format +msgid "SHA1 id %s is not known" +msgstr "SHA1 идентификатор %s не найден" + +#: gitk:7786 +#, tcl-format +msgid "Tag/Head %s is not known" +msgstr "Метка или ветвь %s не найдена" + +#: gitk:7928 +msgid "Children" +msgstr "Потомки" + +#: gitk:7985 +#, tcl-format +msgid "Reset %s branch to here" +msgstr "УÑтановить ветвь %s на Ñто ÑоÑтоÑние" + +#: gitk:7987 +msgid "Detached head: can't reset" +msgstr "СоÑтоÑние не принадлежит ни одной ветви, переход невозможен" + +#: gitk:8019 +msgid "Top" +msgstr "Верх" + +#: gitk:8020 +msgid "From" +msgstr "От" + +#: gitk:8025 +msgid "To" +msgstr "До" + +#: gitk:8049 +msgid "Generate patch" +msgstr "Создать патч" + +#: gitk:8051 +msgid "From:" +msgstr "От:" + +#: gitk:8060 +msgid "To:" +msgstr "До:" + +#: gitk:8069 +msgid "Reverse" +msgstr "Ð’ обратном порÑдке" + +#: gitk:8071 gitk:8253 +msgid "Output file:" +msgstr "Файл Ð´Ð»Ñ ÑохранениÑ:" + +#: gitk:8077 +msgid "Generate" +msgstr "Создать" + +#: gitk:8115 +msgid "Error creating patch:" +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¿Ð°Ñ‚Ñ‡Ð°:" + +#: gitk:8138 gitk:8241 gitk:8298 +msgid "ID:" +msgstr "ID:" + +#: gitk:8147 +msgid "Tag name:" +msgstr "Ð˜Ð¼Ñ Ð¼ÐµÑ‚ÐºÐ¸:" + +#: gitk:8151 gitk:8307 +msgid "Create" +msgstr "Создать" + +#: gitk:8168 +msgid "No tag name specified" +msgstr "Ðе задано Ð¸Ð¼Ñ Ð¼ÐµÑ‚ÐºÐ¸" + +#: gitk:8172 +#, tcl-format +msgid "Tag \"%s\" already exists" +msgstr "Метка \"%s\" уже ÑущеÑтвует" + +#: gitk:8178 +msgid "Error creating tag:" +msgstr "Ошибка ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð¼ÐµÑ‚ÐºÐ¸:" + +#: gitk:8250 +msgid "Command:" +msgstr "Команда:" + +#: gitk:8258 +msgid "Write" +msgstr "ЗапиÑÑŒ" + +#: gitk:8276 +msgid "Error writing commit:" +msgstr "Ошибка ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ ÑоÑтоÑниÑ:" + +#: gitk:8303 +msgid "Name:" +msgstr "ИмÑ:" + +#: gitk:8326 +msgid "Please specify a name for the new branch" +msgstr "Укажите Ð¸Ð¼Ñ Ð´Ð»Ñ Ð½Ð¾Ð²Ð¾Ð¹ ветви" + +#: gitk:8331 +#, tcl-format +msgid "Branch '%s' already exists. Overwrite?" +msgstr "Ветвь '%s' уже ÑущеÑтвует. ПерепиÑать?" + +#: gitk:8397 +#, tcl-format +msgid "Commit %s is already included in branch %s -- really re-apply it?" +msgstr "" +"СоÑтоÑние %s уже принадлежит ветви %s. Продолжить операцию?" + +#: gitk:8402 +msgid "Cherry-picking" +msgstr "Копирование изменений" + +#: gitk:8411 +#, tcl-format +msgid "" +"Cherry-pick failed because of local changes to file '%s'.\n" +"Please commit, reset or stash your changes and try again." +msgstr "" +"Копирование невозможно из-за изменений в файле '%s'.\n" +"Сохраните или отмените Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ повторите операцию." + +#: gitk:8417 +msgid "" +"Cherry-pick failed because of merge conflict.\n" +"Do you wish to run git citool to resolve it?" +msgstr "" +"Копирование изменений невозможно из-за незавершённой операции " +"ÑлиÑниÑ.\nЗапуÑтить git citool Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñтой операции?" + +#: gitk:8433 +msgid "No changes committed" +msgstr "Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð½Ðµ Ñохранены" + +#: gitk:8459 +msgid "Confirm reset" +msgstr "Подтвердите операцию перехода" + +#: gitk:8461 +#, tcl-format +msgid "Reset branch %s to %s?" +msgstr "УÑтановить ветвь %s на ÑоÑтоÑние %s?" + +#: gitk:8465 +msgid "Reset type:" +msgstr "Тип операции перехода:" + +#: gitk:8469 +msgid "Soft: Leave working tree and index untouched" +msgstr "Лёгкий: оÑтавить рабочий каталог и Ð¸Ð½Ð´ÐµÐºÑ Ð½ÐµÐ¸Ð·Ð¼ÐµÐ½Ð½Ñ‹Ð¼Ð¸" + +#: gitk:8472 +msgid "Mixed: Leave working tree untouched, reset index" +msgstr "" +"Смешаный: оÑтавить рабочий каталог неизменным, уÑтановить индекÑ" + +#: gitk:8475 +msgid "" +"Hard: Reset working tree and index\n" +"(discard ALL local changes)" +msgstr "" +"ЖеÑткий: перепиÑать Ð¸Ð½Ð´ÐµÐºÑ Ð¸ рабочий каталог\n" +"(вÑе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² рабочем каталоги будут потерÑны)" + +#: gitk:8492 +msgid "Resetting" +msgstr "УÑтановка" + +#: gitk:8549 +msgid "Checking out" +msgstr "Переход" + +#: gitk:8602 +msgid "Cannot delete the currently checked-out branch" +msgstr "ÐÐºÑ‚Ð¸Ð²Ð½Ð°Ñ Ð²ÐµÑ‚Ð²ÑŒ не может быть удалена" + +#: gitk:8608 +#, tcl-format +msgid "" +"The commits on branch %s aren't on any other branch.\n" +"Really delete branch %s?" +msgstr "" +"СоÑтоÑÐ½Ð¸Ñ Ð²ÐµÑ‚Ð²Ð¸ %s больше не принадлежат никакой другой ветви.\n" +"ДейÑтвительно удалить ветвь %s?" + +#: gitk:8639 +#, tcl-format +msgid "Tags and heads: %s" +msgstr "Метки и ветви: %s" + +#: gitk:8654 +msgid "Filter" +msgstr "Фильтровать" + +#: gitk:8949 +msgid "" +"Error reading commit topology information; branch and preceding/following " +"tag information will be incomplete." +msgstr "" +"Ошибка Ñ‡Ñ‚ÐµÐ½Ð¸Ñ Ð¸Ñтории проекта; Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ветвÑÑ… и ÑоÑтоÑниÑÑ… " +"вокруг меток (до/поÑле) может быть неполной." + +#: gitk:9935 +msgid "Tag" +msgstr "Метка" + +#: gitk:9935 +msgid "Id" +msgstr "Id" + +#: gitk:9983 +msgid "Gitk font chooser" +msgstr "Шрифт Gitk" + +#: gitk:10000 +msgid "B" +msgstr "Ж" + +#: gitk:10003 +msgid "I" +msgstr "К" + +#: gitk:10098 +msgid "Gitk preferences" +msgstr "ÐаÑтройки Gitk" + +#: gitk:10100 +msgid "Commit list display options" +msgstr "Параметры показа ÑпиÑка ÑоÑтоÑний" + +#: gitk:10103 +msgid "Maximum graph width (lines)" +msgstr "МакÑ. ширина графа (Ñтрок)" + +#: gitk:10107 +#, tcl-format +msgid "Maximum graph width (% of pane)" +msgstr "МакÑ. ширина графа (% ширины панели)" + +#: gitk:10111 +msgid "Show local changes" +msgstr "Показывать Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² рабочем каталоге" + +#: gitk:10114 +msgid "Auto-select SHA1" +msgstr "Выделить SHA1" + +#: gitk:10118 +msgid "Diff display options" +msgstr "Параметры показа изменений" + +#: gitk:10120 +msgid "Tab spacing" +msgstr "Ширина табулÑции" + +#: gitk:10123 +msgid "Display nearby tags" +msgstr "Показывать близкие метки" + +#: gitk:10126 +msgid "Limit diffs to listed paths" +msgstr "Ограничить показ изменений выбраными файлами" + +#: gitk:10129 +msgid "Support per-file encodings" +msgstr "Поддержка кодировок в отдельных файлах" + +#: gitk:10135 +msgid "External diff tool" +msgstr "Программа Ð´Ð»Ñ Ð¿Ð¾ÐºÐ°Ð·Ð° изменений" + +#: gitk:10137 +msgid "Choose..." +msgstr "Выберите..." + +#: gitk:10142 +msgid "Colors: press to choose" +msgstr "Цвета: нажмите Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð°" + +#: gitk:10145 +msgid "Background" +msgstr "Фон" + +#: gitk:10146 gitk:10176 +msgid "background" +msgstr "фон" + +#: gitk:10149 +msgid "Foreground" +msgstr "Передний план" + +#: gitk:10150 +msgid "foreground" +msgstr "передний план" + +#: gitk:10153 +msgid "Diff: old lines" +msgstr "ИзменениÑ: Ñтарый текÑÑ‚" + +#: gitk:10154 +msgid "diff old lines" +msgstr "Ñтарый текÑÑ‚ изменениÑ" + +#: gitk:10158 +msgid "Diff: new lines" +msgstr "ИзменениÑ: новый текÑÑ‚" + +#: gitk:10159 +msgid "diff new lines" +msgstr "новый текÑÑ‚ изменениÑ" + +#: gitk:10163 +msgid "Diff: hunk header" +msgstr "ИзменениÑ: заголовок блока" + +#: gitk:10165 +msgid "diff hunk header" +msgstr "заголовок блока изменений" + +#: gitk:10169 +msgid "Marked line bg" +msgstr "Фон выбраной Ñтроки" + +#: gitk:10171 +msgid "marked line background" +msgstr "фон выбраной Ñтроки" + +#: gitk:10175 +msgid "Select bg" +msgstr "Выберите фон" + +#: gitk:10179 +msgid "Fonts: press to choose" +msgstr "Шрифт: нажмите Ð´Ð»Ñ Ð²Ñ‹Ð±Ð¾Ñ€Ð°" + +#: gitk:10181 +msgid "Main font" +msgstr "ОÑновной шрифт" + +#: gitk:10182 +msgid "Diff display font" +msgstr "Шрифт показа изменений" + +#: gitk:10183 +msgid "User interface font" +msgstr "Шрифт интерфейÑа" + +#: gitk:10210 +#, tcl-format +msgid "Gitk: choose color for %s" +msgstr "Gitk: выберите цвет Ð´Ð»Ñ %s" + +#: gitk:10656 +msgid "" +"Sorry, gitk cannot run with this version of Tcl/Tk.\n" +" Gitk requires at least Tcl/Tk 8.4." +msgstr "" +"К Ñожалению gitk не может работать Ñ Ñтой верÑий Tcl/Tk.\n" +"ТребуетÑÑ ÐºÐ°Ðº минимум Tcl/Tk 8.4." + +#: gitk:10773 +msgid "Cannot find a git repository here." +msgstr "Git-репозитарий не найден в текущем каталоге." + +#: gitk:10777 +#, tcl-format +msgid "Cannot find the git directory \"%s\"." +msgstr "Git-репозитарий \"%s\" не найден." + +#: gitk:10824 +#, tcl-format +msgid "Ambiguous argument '%s': both revision and filename" +msgstr "Ðеоднозначный аргумент '%s': ÑущеÑтвует как верÑÐ¸Ñ Ð¸ Ð¸Ð¼Ñ Ñ„Ð°Ð¹Ð»Ð°" + +#: gitk:10836 +msgid "Bad arguments to gitk:" +msgstr "Ðеправильные аргументы Ð´Ð»Ñ gitk:" + +#: gitk:10896 +msgid "Command line" +msgstr "ÐšÐ¾Ð¼Ð°Ð½Ð´Ð½Ð°Ñ Ñтрока" + diff --git a/gitweb/README b/gitweb/README index 8433dd1d45..ccda890c0e 100644 --- a/gitweb/README +++ b/gitweb/README @@ -206,7 +206,7 @@ not include variables usually directly set during build): * $fallback_encoding Gitweb assumes this charset if line contains non-UTF-8 characters. Fallback decoding is used without error checking, so it can be even - 'utf-8'. Value mist be valid encodig; see Encoding::Supported(3pm) man + 'utf-8'. Value must be valid encoding; see Encoding::Supported(3pm) man page for a list. By default 'latin1', aka. 'iso-8859-1'. * @diff_opts Rename detection options for git-diff and git-diff-tree. By default diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 33ef190ceb..1e7e2d8387 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -458,8 +458,8 @@ sub filter_snapshot_fmts { @fmts = map { exists $known_snapshot_format_aliases{$_} ? $known_snapshot_format_aliases{$_} : $_} @fmts; - @fmts = grep(exists $known_snapshot_formats{$_}, @fmts); - + @fmts = grep { + exists $known_snapshot_formats{$_} } @fmts; } our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; @@ -688,11 +688,12 @@ sub evaluate_path_info { # extensions. Allowed extensions are both the defined suffix # (which includes the initial dot already) and the snapshot # format key itself, with a prepended dot - while (my ($fmt, %opt) = each %known_snapshot_formats) { + while (my ($fmt, $opt) = each %known_snapshot_formats) { my $hash = $refname; - my $sfx; - $hash =~ s/(\Q$opt{'suffix'}\E|\Q.$fmt\E)$//; - next unless $sfx = $1; + unless ($hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//) { + next; + } + my $sfx = $1; # a valid suffix was found, so set the snapshot format # and reset the hash parameter $input_params{'snapshot_format'} = $fmt; @@ -828,7 +829,7 @@ if (!defined $action) { if (!defined($actions{$action})) { die_error(400, "Unknown action"); } -if ($action !~ m/^(opml|project_list|project_index)$/ && +if ($action !~ m/^(?:opml|project_list|project_index)$/ && !$project) { die_error(400, "Project needed"); } @@ -838,7 +839,7 @@ exit; ## ====================================================================== ## action links -sub href (%) { +sub href { my %params = @_; # default is to use -absolute url() i.e. $my_uri my $href = $params{-full} ? $my_url : $my_uri; @@ -1036,7 +1037,7 @@ sub esc_url { } # replace invalid utf8 character with SUBSTITUTION sequence -sub esc_html ($;%) { +sub esc_html { my $str = shift; my %opts = @_; @@ -1235,7 +1236,7 @@ sub chop_and_escape_str { if ($chopped eq $str) { return esc_html($chopped); } else { - $str =~ s/([[:cntrl:]])/?/g; + $str =~ s/[[:cntrl:]]/?/g; return $cgi->span({-title=>$str}, esc_html($chopped)); } } @@ -1296,7 +1297,7 @@ use constant { }; # submodule/subproject, a commit object reference -sub S_ISGITLINK($) { +sub S_ISGITLINK { my $mode = shift; return (($mode & S_IFMT) == S_IFGITLINK) @@ -1458,6 +1459,7 @@ sub format_subject_html { $extra = '' unless defined($extra); if (length($short) < length($long)) { + $long =~ s/[[:cntrl:]]/?/g; return $cgi->a({-href => $href, -class => "list subject", -title => to_utf8($long)}, esc_html($short) . $extra); @@ -1838,7 +1840,7 @@ sub git_cmd { # Try to avoid using this function wherever possible. sub quote_command { return join(' ', - map( { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ )); + map { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ ); } # get HEAD ref of given project as hash @@ -2050,7 +2052,7 @@ sub git_get_project_description { my $path = shift; $git_dir = "$projectroot/$path"; - open my $fd, "$git_dir/description" + open my $fd, '<', "$git_dir/description" or return git_get_project_config('description'); my $descr = <$fd>; close $fd; @@ -2065,18 +2067,17 @@ sub git_get_project_ctags { my $ctags = {}; $git_dir = "$projectroot/$path"; - unless (opendir D, "$git_dir/ctags") { - return $ctags; - } - foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir(D)) { - open CT, $_ or next; - my $val = <CT>; + opendir my $dh, "$git_dir/ctags" + or return $ctags; + foreach (grep { -f $_ } map { "$git_dir/ctags/$_" } readdir($dh)) { + open my $ct, '<', $_ or next; + my $val = <$ct>; chomp $val; - close CT; + close $ct; my $ctag = $_; $ctag =~ s#.*/##; $ctags->{$ctag} = $val; } - closedir D; + closedir $dh; $ctags; } @@ -2129,7 +2130,7 @@ sub git_get_project_url_list { my $path = shift; $git_dir = "$projectroot/$path"; - open my $fd, "$git_dir/cloneurl" + open my $fd, '<', "$git_dir/cloneurl" or return wantarray ? @{ config_to_multi(git_get_project_config('url')) } : config_to_multi(git_get_project_config('url')); @@ -2187,7 +2188,7 @@ sub git_get_projects_list { # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' my %paths; - open my ($fd), $projects_list or return; + open my $fd, '<', $projects_list or return; PROJECT: while (my $line = <$fd>) { chomp $line; @@ -2250,7 +2251,7 @@ sub git_get_project_list_from_file { # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' if (-f $projects_list) { - open (my $fd , $projects_list); + open(my $fd, '<', $projects_list); while (my $line = <$fd>) { chomp $line; my ($pr, $ow) = split ' ', $line; @@ -2615,7 +2616,7 @@ sub parsed_difftree_line { } # parse line of git-ls-tree output -sub parse_ls_tree_line ($;%) { +sub parse_ls_tree_line { my $line = shift; my %opts = @_; my %res; @@ -2804,18 +2805,18 @@ sub mimetype_guess_file { -r $mimemap or return undef; my %mimemap; - open(MIME, $mimemap) or return undef; - while (<MIME>) { + open(my $mh, '<', $mimemap) or return undef; + while (<$mh>) { next if m/^#/; # skip comments - my ($mime, $exts) = split(/\t+/); + my ($mimetype, $exts) = split(/\t+/); if (defined $exts) { my @exts = split(/\s+/, $exts); foreach my $ext (@exts) { - $mimemap{$ext} = $mime; + $mimemap{$ext} = $mimetype; } } } - close(MIME); + close($mh); $filename =~ /\.([^.]*)$/; return $mimemap{$1}; @@ -3213,7 +3214,6 @@ sub git_print_header_div { "\n</div>\n"; } -#sub git_print_authorship (\%) { sub git_print_authorship { my $co = shift; @@ -3269,8 +3269,7 @@ sub git_print_page_path { print "<br/></div>\n"; } -# sub git_print_log (\@;%) { -sub git_print_log ($;%) { +sub git_print_log { my $log = shift; my %opts = @_; @@ -3328,7 +3327,7 @@ sub git_get_link_target { open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash or return; { - local $/; + local $/ = undef; $link_target = <$fd>; } close $fd @@ -3341,10 +3340,7 @@ sub git_get_link_target { # return target of link relative to top directory (top tree); # return undef if it is not possible (including absolute links). sub normalize_link_target { - my ($link_target, $basedir, $hash_base) = @_; - - # we can normalize symlink target only if $hash_base is provided - return unless $hash_base; + my ($link_target, $basedir) = @_; # absolute symlinks (beginning with '/') cannot be normalized return if (substr($link_target, 0, 1) eq '/'); @@ -3400,7 +3396,7 @@ sub git_print_tree_entry { if (S_ISLNK(oct $t->{'mode'})) { my $link_target = git_get_link_target($t->{'hash'}); if ($link_target) { - my $norm_target = normalize_link_target($link_target, $basedir, $hash_base); + my $norm_target = normalize_link_target($link_target, $basedir); if (defined $norm_target) { print " -> " . $cgi->a({-href => href(action=>"object", hash_base=>$hash_base, @@ -3993,7 +3989,7 @@ sub fill_project_list_info { ($pname !~ /\/$/) && (-d "$projectroot/$pname")) { $pr->{'forks'} = "-d $projectroot/$pname"; - } else { + } else { $pr->{'forks'} = 0; } } @@ -4803,11 +4799,10 @@ sub git_blob_plain { -content_disposition => ($sandbox ? 'attachment' : 'inline') . '; filename="' . $save_as . '"'); - undef $/; + local $/ = undef; binmode STDOUT, ':raw'; print <$fd>; binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi - $/ = "\n"; close $fd; } @@ -4909,12 +4904,16 @@ sub git_tree { } } die_error(404, "No such tree") unless defined($hash); - $/ = "\0"; - open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash - or die_error(500, "Open git-ls-tree failed"); - my @entries = map { chomp; $_ } <$fd>; - close $fd or die_error(404, "Reading tree failed"); - $/ = "\n"; + + my @entries = (); + { + local $/ = "\0"; + open my $fd, "-|", git_cmd(), "ls-tree", '-z', $hash + or die_error(500, "Open git-ls-tree failed"); + @entries = map { chomp; $_ } <$fd>; + close $fd + or die_error(404, "Reading tree failed"); + } my $refs = git_get_references(); my $ref = format_ref_marker($refs, $hash_base); @@ -5809,7 +5808,7 @@ sub git_search { print "<table class=\"pickaxe search\">\n"; my $alternate = 1; - $/ = "\n"; + local $/ = "\n"; open my $fd, '-|', git_cmd(), '--no-pager', 'log', @diff_opts, '--pretty=format:%H', '--no-abbrev', '--raw', "-S$searchtext", ($search_use_regexp ? '--pickaxe-regex' : ()); @@ -5879,7 +5878,7 @@ sub git_search { print "<table class=\"grep_search\">\n"; my $alternate = 1; my $matches = 0; - $/ = "\n"; + local $/ = "\n"; open my $fd, "-|", git_cmd(), 'grep', '-n', $search_use_regexp ? ('-E', '-i') : '-F', $searchtext, $co{'tree'}; @@ -6282,7 +6281,7 @@ XML # end of feed if ($format eq 'rss') { print "</channel>\n</rss>\n"; - } elsif ($format eq 'atom') { + } elsif ($format eq 'atom') { print "</feed>\n"; } } @@ -1,5 +1,6 @@ #include "cache.h" #include "commit.h" +#include "color.h" #include "graph.h" #include "diff.h" #include "revision.h" @@ -34,7 +35,7 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb); * newline. A new graph line will not be printed after the final newline. * If the strbuf is empty, no output will be printed. * - * Since the first line will not include the graph ouput, the caller is + * Since the first line will not include the graph output, the caller is * responsible for printing this line's graph (perhaps via * graph_show_commit() or graph_show_oneline()) before calling * graph_show_strbuf(). @@ -43,27 +44,9 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb); /* * TODO: - * - Add colors to the graph. - * Pick a color for each column, and print all characters - * in that column with the specified color. - * * - Limit the number of columns, similar to the way gitk does. * If we reach more than a specified number of columns, omit * sections of some columns. - * - * - The output during the GRAPH_PRE_COMMIT and GRAPH_COLLAPSING states - * could be made more compact by printing horizontal lines, instead of - * long diagonal lines. For example, during collapsing, something like - * this: instead of this: - * | | | | | | | | | | - * | |_|_|/ | | | |/ - * |/| | | | | |/| - * | | | | | |/| | - * |/| | | - * | | | | - * - * If there are several parallel diagonal lines, they will need to be - * replaced with horizontal lines on subsequent rows. */ struct column { @@ -72,9 +55,10 @@ struct column { */ struct commit *commit; /* - * XXX: Once we add support for colors, struct column could also - * contain the color of its branch line. + * The color to (optionally) print this column in. This is an + * index into column_colors. */ + unsigned short color; }; enum graph_state { @@ -86,6 +70,41 @@ enum graph_state { GRAPH_COLLAPSING }; +/* + * The list of available column colors. + */ +static char column_colors[][COLOR_MAXLEN] = { + GIT_COLOR_RED, + GIT_COLOR_GREEN, + GIT_COLOR_YELLOW, + GIT_COLOR_BLUE, + GIT_COLOR_MAGENTA, + GIT_COLOR_CYAN, + GIT_COLOR_BOLD GIT_COLOR_RED, + GIT_COLOR_BOLD GIT_COLOR_GREEN, + GIT_COLOR_BOLD GIT_COLOR_YELLOW, + GIT_COLOR_BOLD GIT_COLOR_BLUE, + GIT_COLOR_BOLD GIT_COLOR_MAGENTA, + GIT_COLOR_BOLD GIT_COLOR_CYAN, +}; + +#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors)) + +static const char *column_get_color_code(const struct column *c) +{ + return column_colors[c->color]; +} + +static void strbuf_write_column(struct strbuf *sb, const struct column *c, + char col_char) +{ + if (c->color < COLUMN_COLORS_MAX) + strbuf_addstr(sb, column_get_color_code(c)); + strbuf_addch(sb, col_char); + if (c->color < COLUMN_COLORS_MAX) + strbuf_addstr(sb, GIT_COLOR_RESET); +} + struct git_graph { /* * The commit currently being processed @@ -185,6 +204,11 @@ struct git_graph { * temporary array each time we have to output a collapsing line. */ int *new_mapping; + /* + * The current default column color being used. This is + * stored as an index into the array column_colors. + */ + unsigned short default_column_color; }; struct git_graph *graph_init(struct rev_info *opt) @@ -201,6 +225,7 @@ struct git_graph *graph_init(struct rev_info *opt) graph->num_columns = 0; graph->num_new_columns = 0; graph->mapping_size = 0; + graph->default_column_color = 0; /* * Allocate a reasonably large default number of columns @@ -312,6 +337,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph) return next_interesting_parent(graph, parents); } +static unsigned short graph_get_current_column_color(const struct git_graph *graph) +{ + if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF)) + return COLUMN_COLORS_MAX; + return graph->default_column_color; +} + +/* + * Update the graph's default column color. + */ +static void graph_increment_column_color(struct git_graph *graph) +{ + graph->default_column_color = (graph->default_column_color + 1) % + COLUMN_COLORS_MAX; +} + +static unsigned short graph_find_commit_color(const struct git_graph *graph, + const struct commit *commit) +{ + int i; + for (i = 0; i < graph->num_columns; i++) { + if (graph->columns[i].commit == commit) + return graph->columns[i].color; + } + return graph_get_current_column_color(graph); +} + static void graph_insert_into_new_columns(struct git_graph *graph, struct commit *commit, int *mapping_index) @@ -334,6 +386,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph, * This commit isn't already in new_columns. Add it. */ graph->new_columns[graph->num_new_columns].commit = commit; + graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit); graph->mapping[*mapping_index] = graph->num_new_columns; *mapping_index += 2; graph->num_new_columns++; @@ -445,6 +498,12 @@ static void graph_update_columns(struct git_graph *graph) for (parent = first_interesting_parent(graph); parent; parent = next_interesting_parent(graph, parent)) { + /* + * If this is a merge increment the current + * color. + */ + if (graph->num_parents > 1) + graph_increment_column_color(graph); graph_insert_into_new_columns(graph, parent->item, &mapping_idx); @@ -560,7 +619,8 @@ static int graph_is_mapping_correct(struct git_graph *graph) return 1; } -static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb) +static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb, + int chars_written) { /* * Add additional spaces to the end of the strbuf, so that all @@ -570,10 +630,10 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb) * aligned for the entire commit. */ int extra; - if (sb->len >= graph->width) + if (chars_written >= graph->width) return; - extra = graph->width - sb->len; + extra = graph->width - chars_written; strbuf_addf(sb, "%*s", (int) extra, ""); } @@ -596,10 +656,11 @@ static void graph_output_padding_line(struct git_graph *graph, * Output a padding row, that leaves all branch lines unchanged */ for (i = 0; i < graph->num_new_columns; i++) { - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, &graph->new_columns[i], '|'); + strbuf_addch(sb, ' '); } - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, graph->num_new_columns * 2); } static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb) @@ -609,7 +670,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb) * of the graph is missing. */ strbuf_addstr(sb, "..."); - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, 3); if (graph->num_parents >= 3 && graph->commit_index < (graph->num_columns - 1)) @@ -623,6 +684,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph, { int num_expansion_rows; int i, seen_this; + int chars_written; /* * This function formats a row that increases the space around a commit @@ -645,11 +707,14 @@ static void graph_output_pre_commit_line(struct git_graph *graph, * Output the row */ seen_this = 0; + chars_written = 0; for (i = 0; i < graph->num_columns; i++) { struct column *col = &graph->columns[i]; if (col->commit == graph->commit) { seen_this = 1; - strbuf_addf(sb, "| %*s", graph->expansion_row, ""); + strbuf_write_column(sb, col, '|'); + strbuf_addf(sb, "%*s", graph->expansion_row, ""); + chars_written += 1 + graph->expansion_row; } else if (seen_this && (graph->expansion_row == 0)) { /* * This is the first line of the pre-commit output. @@ -662,17 +727,22 @@ static void graph_output_pre_commit_line(struct git_graph *graph, */ if (graph->prev_state == GRAPH_POST_MERGE && graph->prev_commit_index < i) - strbuf_addstr(sb, "\\ "); + strbuf_write_column(sb, col, '\\'); else - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, col, '|'); + chars_written++; } else if (seen_this && (graph->expansion_row > 0)) { - strbuf_addstr(sb, "\\ "); + strbuf_write_column(sb, col, '\\'); + chars_written++; } else { - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, col, '|'); + chars_written++; } + strbuf_addch(sb, ' '); + chars_written++; } - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, chars_written); /* * Increment graph->expansion_row, @@ -714,10 +784,34 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb) strbuf_addch(sb, '*'); } +/* + * Draw an octopus merge and return the number of characters written. + */ +static int graph_draw_octopus_merge(struct git_graph *graph, + struct strbuf *sb) +{ + /* + * Here dashless_commits represents the number of parents + * which don't need to have dashes (because their edges fit + * neatly under the commit). + */ + const int dashless_commits = 2; + int col_num, i; + int num_dashes = + ((graph->num_parents - dashless_commits) * 2) - 1; + for (i = 0; i < num_dashes; i++) { + col_num = (i / 2) + dashless_commits; + strbuf_write_column(sb, &graph->new_columns[col_num], '-'); + } + col_num = (i / 2) + dashless_commits; + strbuf_write_column(sb, &graph->new_columns[col_num], '.'); + return num_dashes + 1; +} + static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) { int seen_this = 0; - int i, j; + int i, chars_written; /* * Output the row containing this commit @@ -727,7 +821,9 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) * children that we have already processed.) */ seen_this = 0; + chars_written = 0; for (i = 0; i <= graph->num_columns; i++) { + struct column *col = &graph->columns[i]; struct commit *col_commit; if (i == graph->num_columns) { if (seen_this) @@ -740,18 +836,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) if (col_commit == graph->commit) { seen_this = 1; graph_output_commit_char(graph, sb); + chars_written++; - if (graph->num_parents < 3) - strbuf_addch(sb, ' '); - else { - int num_dashes = - ((graph->num_parents - 2) * 2) - 1; - for (j = 0; j < num_dashes; j++) - strbuf_addch(sb, '-'); - strbuf_addstr(sb, ". "); - } + if (graph->num_parents > 2) + chars_written += graph_draw_octopus_merge(graph, + sb); } else if (seen_this && (graph->num_parents > 2)) { - strbuf_addstr(sb, "\\ "); + strbuf_write_column(sb, col, '\\'); + chars_written++; } else if (seen_this && (graph->num_parents == 2)) { /* * This is a 2-way merge commit. @@ -768,15 +860,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) */ if (graph->prev_state == GRAPH_POST_MERGE && graph->prev_commit_index < i) - strbuf_addstr(sb, "\\ "); + strbuf_write_column(sb, col, '\\'); else - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, col, '|'); + chars_written++; } else { - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, col, '|'); + chars_written++; } + strbuf_addch(sb, ' '); + chars_written++; } - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, chars_written); /* * Update graph->state @@ -789,37 +885,75 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb) graph_update_state(graph, GRAPH_COLLAPSING); } +static struct column *find_new_column_by_commit(struct git_graph *graph, + struct commit *commit) +{ + int i; + for (i = 0; i < graph->num_new_columns; i++) { + if (graph->new_columns[i].commit == commit) + return &graph->new_columns[i]; + } + return 0; +} + static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb) { int seen_this = 0; - int i, j; + int i, j, chars_written; /* * Output the post-merge row */ + chars_written = 0; for (i = 0; i <= graph->num_columns; i++) { + struct column *col = &graph->columns[i]; struct commit *col_commit; if (i == graph->num_columns) { if (seen_this) break; col_commit = graph->commit; } else { - col_commit = graph->columns[i].commit; + col_commit = col->commit; } if (col_commit == graph->commit) { + /* + * Since the current commit is a merge find + * the columns for the parent commits in + * new_columns and use those to format the + * edges. + */ + struct commit_list *parents = NULL; + struct column *par_column; seen_this = 1; - strbuf_addch(sb, '|'); - for (j = 0; j < graph->num_parents - 1; j++) - strbuf_addstr(sb, "\\ "); + parents = first_interesting_parent(graph); + assert(parents); + par_column = find_new_column_by_commit(graph, parents->item); + assert(par_column); + + strbuf_write_column(sb, par_column, '|'); + chars_written++; + for (j = 0; j < graph->num_parents - 1; j++) { + parents = next_interesting_parent(graph, parents); + assert(parents); + par_column = find_new_column_by_commit(graph, parents->item); + assert(par_column); + strbuf_write_column(sb, par_column, '\\'); + strbuf_addch(sb, ' '); + } + chars_written += j * 2; } else if (seen_this) { - strbuf_addstr(sb, "\\ "); + strbuf_write_column(sb, col, '\\'); + strbuf_addch(sb, ' '); + chars_written += 2; } else { - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, col, '|'); + strbuf_addch(sb, ' '); + chars_written += 2; } } - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, chars_written); /* * Update graph->state @@ -834,6 +968,9 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf { int i; int *tmp_mapping; + short used_horizontal = 0; + int horizontal_edge = -1; + int horizontal_edge_target = -1; /* * Clear out the new_mapping array @@ -871,6 +1008,23 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * Move to the left by one */ graph->new_mapping[i - 1] = target; + /* + * If there isn't already an edge moving horizontally + * select this one. + */ + if (horizontal_edge == -1) { + int j; + horizontal_edge = i; + horizontal_edge_target = target; + /* + * The variable target is the index of the graph + * column, and therefore target*2+3 is the + * actual screen column of the first horizontal + * line. + */ + for (j = (target * 2)+3; j < (i - 2); j += 2) + graph->new_mapping[j] = target; + } } else if (graph->new_mapping[i - 1] == target) { /* * There is a branch line to our left @@ -891,10 +1045,21 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf * * The space just to the left of this * branch should always be empty. + * + * The branch to the left of that space + * should be our eventual target. */ assert(graph->new_mapping[i - 1] > target); assert(graph->new_mapping[i - 2] < 0); + assert(graph->new_mapping[i - 3] == target); graph->new_mapping[i - 2] = target; + /* + * Mark this branch as the horizontal edge to + * prevent any other edges from moving + * horizontally. + */ + if (horizontal_edge == -1) + horizontal_edge = i; } } @@ -912,12 +1077,27 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf if (target < 0) strbuf_addch(sb, ' '); else if (target * 2 == i) - strbuf_addch(sb, '|'); - else - strbuf_addch(sb, '/'); + strbuf_write_column(sb, &graph->new_columns[target], '|'); + else if (target == horizontal_edge_target && + i != horizontal_edge - 1) { + /* + * Set the mappings for all but the + * first segment to -1 so that they + * won't continue into the next line. + */ + if (i != (target * 2)+3) + graph->new_mapping[i] = -1; + used_horizontal = 1; + strbuf_write_column(sb, &graph->new_columns[target], '_'); + } else { + if (used_horizontal && i < horizontal_edge) + graph->new_mapping[i] = -1; + strbuf_write_column(sb, &graph->new_columns[target], '/'); + + } } - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, graph->mapping_size); /* * Swap mapping and new_mapping @@ -979,9 +1159,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) * children that we have already processed.) */ for (i = 0; i < graph->num_columns; i++) { - struct commit *col_commit = graph->columns[i].commit; + struct column *col = &graph->columns[i]; + struct commit *col_commit = col->commit; if (col_commit == graph->commit) { - strbuf_addch(sb, '|'); + strbuf_write_column(sb, col, '|'); if (graph->num_parents < 3) strbuf_addch(sb, ' '); @@ -991,11 +1172,12 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb) strbuf_addch(sb, ' '); } } else { - strbuf_addstr(sb, "| "); + strbuf_write_column(sb, col, '|'); + strbuf_addch(sb, ' '); } } - graph_pad_horizontally(graph, sb); + graph_pad_horizontally(graph, sb, graph->num_columns); /* * Update graph->prev_state since we have output a padding line @@ -39,6 +39,8 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) { int err; + p->word_regexp = opt->word_regexp; + if (opt->fixed || is_fixed(p->pattern)) p->fixed = 1; if (opt->regflags & REG_ICASE) @@ -192,7 +194,8 @@ void compile_grep_patterns(struct grep_opt *opt) * A classic recursive descent parser would do. */ p = opt->pattern_list; - opt->pattern_expression = compile_pattern_expr(&p); + if (p) + opt->pattern_expression = compile_pattern_expr(&p); if (p) die("incomplete pattern expression: %s", p->pattern); } @@ -253,18 +256,6 @@ static int word_char(char ch) return isalnum(ch) || ch == '_'; } -static void show_line(struct grep_opt *opt, const char *bol, const char *eol, - const char *name, unsigned lno, char sign) -{ - if (opt->null_following_name) - sign = '\0'; - if (opt->pathname) - printf("%s%c", name, sign); - if (opt->linenum) - printf("%d%c", lno, sign); - printf("%.*s\n", (int)(eol-bol), bol); -} - static void show_name(struct grep_opt *opt, const char *name) { printf("%s%c", name, opt->null_following_name ? '\0' : '\n'); @@ -308,11 +299,13 @@ static struct { { "committer ", 10 }, }; -static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx) +static int match_one_pattern(struct grep_pat *p, char *bol, char *eol, + enum grep_context ctx, + regmatch_t *pmatch, int eflags) { int hit = 0; int saved_ch = 0; - regmatch_t pmatch[10]; + const char *start = bol; if ((p->token != GREP_PATTERN) && ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD))) @@ -331,18 +324,14 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol } again: - if (!p->fixed) { - regex_t *exp = &p->regexp; - hit = !regexec(exp, bol, ARRAY_SIZE(pmatch), - pmatch, 0); - } - else { + if (p->fixed) hit = !fixmatch(p->pattern, bol, pmatch); - } + else + hit = !regexec(&p->regexp, bol, 1, pmatch, eflags); - if (hit && opt->word_regexp) { + if (hit && p->word_regexp) { if ((pmatch[0].rm_so < 0) || - (eol - bol) <= pmatch[0].rm_so || + (eol - bol) < pmatch[0].rm_so || (pmatch[0].rm_eo < 0) || (eol - bol) < pmatch[0].rm_eo) die("regexp returned nonsense"); @@ -361,6 +350,10 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol else hit = 0; + /* Words consist of at least one character. */ + if (pmatch->rm_so == pmatch->rm_eo) + hit = 0; + if (!hit && pmatch[0].rm_so + bol + 1 < eol) { /* There could be more than one match on the * line, and the first match might not be @@ -371,50 +364,49 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol bol = pmatch[0].rm_so + bol + 1; while (word_char(bol[-1]) && bol < eol) bol++; + eflags |= REG_NOTBOL; if (bol < eol) goto again; } } if (p->token == GREP_PATTERN_HEAD && saved_ch) *eol = saved_ch; + if (hit) { + pmatch[0].rm_so += bol - start; + pmatch[0].rm_eo += bol - start; + } return hit; } -static int match_expr_eval(struct grep_opt *o, - struct grep_expr *x, - char *bol, char *eol, - enum grep_context ctx, - int collect_hits) +static int match_expr_eval(struct grep_expr *x, char *bol, char *eol, + enum grep_context ctx, int collect_hits) { int h = 0; + regmatch_t match; if (!x) die("Not a valid grep expression"); switch (x->node) { case GREP_NODE_ATOM: - h = match_one_pattern(o, x->u.atom, bol, eol, ctx); + h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0); break; case GREP_NODE_NOT: - h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0); + h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0); break; case GREP_NODE_AND: - if (!collect_hits) - return (match_expr_eval(o, x->u.binary.left, - bol, eol, ctx, 0) && - match_expr_eval(o, x->u.binary.right, - bol, eol, ctx, 0)); - h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0); - h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0); + if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0)) + return 0; + h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0); break; case GREP_NODE_OR: if (!collect_hits) - return (match_expr_eval(o, x->u.binary.left, + return (match_expr_eval(x->u.binary.left, bol, eol, ctx, 0) || - match_expr_eval(o, x->u.binary.right, + match_expr_eval(x->u.binary.right, bol, eol, ctx, 0)); - h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0); + h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0); x->u.binary.left->hit |= h; - h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1); + h |= match_expr_eval(x->u.binary.right, bol, eol, ctx, 1); break; default: die("Unexpected node type (internal error) %d", x->node); @@ -428,24 +420,106 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol, enum grep_context ctx, int collect_hits) { struct grep_expr *x = opt->pattern_expression; - return match_expr_eval(opt, x, bol, eol, ctx, collect_hits); + return match_expr_eval(x, bol, eol, ctx, collect_hits); } static int match_line(struct grep_opt *opt, char *bol, char *eol, enum grep_context ctx, int collect_hits) { struct grep_pat *p; + regmatch_t match; + if (opt->extended) return match_expr(opt, bol, eol, ctx, collect_hits); /* we do not call with collect_hits without being extended */ for (p = opt->pattern_list; p; p = p->next) { - if (match_one_pattern(opt, p, bol, eol, ctx)) + if (match_one_pattern(p, bol, eol, ctx, &match, 0)) return 1; } return 0; } +static int match_next_pattern(struct grep_pat *p, char *bol, char *eol, + enum grep_context ctx, + regmatch_t *pmatch, int eflags) +{ + regmatch_t match; + + if (!match_one_pattern(p, bol, eol, ctx, &match, eflags)) + return 0; + if (match.rm_so < 0 || match.rm_eo < 0) + return 0; + if (pmatch->rm_so >= 0 && pmatch->rm_eo >= 0) { + if (match.rm_so > pmatch->rm_so) + return 1; + if (match.rm_so == pmatch->rm_so && match.rm_eo < pmatch->rm_eo) + return 1; + } + pmatch->rm_so = match.rm_so; + pmatch->rm_eo = match.rm_eo; + return 1; +} + +static int next_match(struct grep_opt *opt, char *bol, char *eol, + enum grep_context ctx, regmatch_t *pmatch, int eflags) +{ + struct grep_pat *p; + int hit = 0; + + pmatch->rm_so = pmatch->rm_eo = -1; + if (bol < eol) { + for (p = opt->pattern_list; p; p = p->next) { + switch (p->token) { + case GREP_PATTERN: /* atom */ + case GREP_PATTERN_HEAD: + case GREP_PATTERN_BODY: + hit |= match_next_pattern(p, bol, eol, ctx, + pmatch, eflags); + break; + default: + break; + } + } + } + return hit; +} + +static void show_line(struct grep_opt *opt, char *bol, char *eol, + const char *name, unsigned lno, char sign) +{ + int rest = eol - bol; + + if (opt->null_following_name) + sign = '\0'; + if (opt->pathname) + printf("%s%c", name, sign); + if (opt->linenum) + printf("%d%c", lno, sign); + if (opt->color) { + regmatch_t match; + enum grep_context ctx = GREP_CONTEXT_BODY; + int ch = *eol; + int eflags = 0; + + *eol = '\0'; + while (next_match(opt, bol, eol, ctx, &match, eflags)) { + if (match.rm_so == match.rm_eo) + break; + printf("%.*s%s%.*s%s", + (int)match.rm_so, bol, + opt->color_match, + (int)(match.rm_eo - match.rm_so), bol + match.rm_so, + GIT_COLOR_RESET); + bol += match.rm_eo; + rest -= match.rm_eo; + eflags = REG_NOTBOL; + } + *eol = ch; + } + printf("%.*s\n", rest, bol); +} + static int grep_buffer_1(struct grep_opt *opt, const char *name, char *buf, unsigned long size, int collect_hits) { @@ -1,5 +1,6 @@ #ifndef GREP_H #define GREP_H +#include "color.h" enum grep_pat_token { GREP_PATTERN, @@ -31,6 +32,7 @@ struct grep_pat { enum grep_header_field field; regex_t regexp; unsigned fixed:1; + unsigned word_regexp:1; }; enum grep_expr_node { @@ -59,23 +61,26 @@ struct grep_opt { struct grep_expr *pattern_expression; int prefix_length; regex_t regexp; - unsigned linenum:1; - unsigned invert:1; - unsigned status_only:1; - unsigned name_only:1; - unsigned unmatch_name_only:1; - unsigned count:1; - unsigned word_regexp:1; - unsigned fixed:1; - unsigned all_match:1; + int linenum; + int invert; + int status_only; + int name_only; + int unmatch_name_only; + int count; + int word_regexp; + int fixed; + int all_match; #define GREP_BINARY_DEFAULT 0 #define GREP_BINARY_NOMATCH 1 #define GREP_BINARY_TEXT 2 - unsigned binary:2; - unsigned extended:1; - unsigned relative:1; - unsigned pathname:1; - unsigned null_following_name:1; + int binary; + int extended; + int relative; + int pathname; + int null_following_name; + int color; + char color_match[COLOR_MAXLEN]; + const char *color_external; int regflags; unsigned pre_context; unsigned post_context; diff --git a/hash-object.c b/hash-object.c index ebb3bedb07..47cf43c3cd 100644 --- a/hash-object.c +++ b/hash-object.c @@ -84,7 +84,8 @@ int main(int argc, const char **argv) git_extract_argv0_path(argv[0]); - argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0); + argc = parse_options(argc, argv, NULL, hash_object_options, + hash_object_usage, 0); if (write_object) { prefix = setup_git_directory(); diff --git a/http-push.c b/http-push.c index f101df4086..e4ea395e02 100644 --- a/http-push.c +++ b/http-push.c @@ -1,6 +1,5 @@ #include "cache.h" #include "commit.h" -#include "pack.h" #include "tag.h" #include "blob.h" #include "http.h" @@ -27,7 +26,6 @@ enum XML_Status { #endif #define PREV_BUF_SIZE 4096 -#define RANGE_HEADER_SIZE 30 /* DAV methods */ #define DAV_LOCK "LOCK" @@ -76,8 +74,6 @@ static int pushing; static int aborted; static signed char remote_dir_exists[256]; -static struct curl_slist *no_pragma_header; - static int push_verbosely; static int push_all = MATCH_REFS_NONE; static int force_all; @@ -97,7 +93,7 @@ struct repo struct remote_lock *locks; }; -static struct repo *remote; +static struct repo *repo; enum transfer_state { NEED_FETCH, @@ -119,19 +115,10 @@ struct transfer_request struct remote_lock *lock; struct curl_slist *headers; struct buffer buffer; - char filename[PATH_MAX]; - char tmpfile[PATH_MAX]; - int local_fileno; - FILE *local_stream; enum transfer_state state; CURLcode curl_result; char errorstr[CURL_ERROR_SIZE]; long http_code; - unsigned char real_sha1[20]; - git_SHA_CTX c; - z_stream stream; - int zret; - int rename; void *userData; struct active_request_slot *slot; struct transfer_request *next; @@ -186,6 +173,32 @@ enum dav_header_flag { DAV_HEADER_TIMEOUT = (1u << 2) }; +static char *xml_entities(char *s) +{ + struct strbuf buf = STRBUF_INIT; + while (*s) { + size_t len = strcspn(s, "\"<>&"); + strbuf_add(&buf, s, len); + s += len; + switch (*s) { + case '"': + strbuf_addstr(&buf, """); + break; + case '<': + strbuf_addstr(&buf, "<"); + break; + case '>': + strbuf_addstr(&buf, ">"); + break; + case '&': + strbuf_addstr(&buf, "&"); + break; + } + s++; + } + return strbuf_detach(&buf, NULL); +} + static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options) { struct strbuf buf = STRBUF_INIT; @@ -211,15 +224,6 @@ static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum d 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); @@ -233,163 +237,29 @@ 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) -{ - unsigned char expn[4096]; - size_t size = eltsize * nmemb; - int posn = 0; - struct transfer_request *request = (struct transfer_request *)data; - do { - ssize_t retval = xwrite(request->local_fileno, - (char *) ptr + posn, size - posn); - if (retval < 0) - return posn; - posn += retval; - } while (posn < size); - - request->stream.avail_in = size; - request->stream.next_in = ptr; - do { - request->stream.next_out = expn; - request->stream.avail_out = sizeof(expn); - request->zret = git_inflate(&request->stream, Z_SYNC_FLUSH); - git_SHA1_Update(&request->c, expn, - sizeof(expn) - request->stream.avail_out); - } while (request->stream.avail_in && request->zret == Z_OK); - data_received++; - return size; -} - static void start_fetch_loose(struct transfer_request *request) { - char *hex = sha1_to_hex(request->obj->sha1); - char *filename; - char prevfile[PATH_MAX]; - char *url; - int prevlocal; - unsigned char prev_buf[PREV_BUF_SIZE]; - ssize_t prev_read = 0; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; struct active_request_slot *slot; + struct http_object_request *obj_req; - filename = sha1_file_name(request->obj->sha1); - snprintf(request->filename, sizeof(request->filename), "%s", filename); - snprintf(request->tmpfile, sizeof(request->tmpfile), - "%s.temp", filename); - - snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename); - unlink(prevfile); - rename(request->tmpfile, prevfile); - unlink(request->tmpfile); - - if (request->local_fileno != -1) - error("fd leakage in start: %d", request->local_fileno); - request->local_fileno = open(request->tmpfile, - O_WRONLY | O_CREAT | O_EXCL, 0666); - /* This could have failed due to the "lazy directory creation"; - * try to mkdir the last path component. - */ - if (request->local_fileno < 0 && errno == ENOENT) { - char *dir = strrchr(request->tmpfile, '/'); - if (dir) { - *dir = 0; - mkdir(request->tmpfile, 0777); - *dir = '/'; - } - request->local_fileno = open(request->tmpfile, - O_WRONLY | O_CREAT | O_EXCL, 0666); - } - - if (request->local_fileno < 0) { + obj_req = new_http_object_request(repo->url, request->obj->sha1); + if (obj_req == NULL) { request->state = ABORTED; - error("Couldn't create temporary file %s for %s: %s", - request->tmpfile, request->filename, strerror(errno)); return; } - memset(&request->stream, 0, sizeof(request->stream)); - - git_inflate_init(&request->stream); - - git_SHA1_Init(&request->c); - - 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. */ - prevlocal = open(prevfile, O_RDONLY); - if (prevlocal != -1) { - do { - prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE); - if (prev_read>0) { - if (fwrite_sha1_file(prev_buf, - 1, - prev_read, - request) == prev_read) { - prev_posn += prev_read; - } else { - prev_read = -1; - } - } - } while (prev_read > 0); - close(prevlocal); - } - unlink(prevfile); - - /* Reset inflate/SHA1 if there was an error reading the previous temp - file; also rewind to the beginning of the local file. */ - if (prev_read == -1) { - memset(&request->stream, 0, sizeof(request->stream)); - git_inflate_init(&request->stream); - git_SHA1_Init(&request->c); - if (prev_posn>0) { - prev_posn = 0; - lseek(request->local_fileno, 0, SEEK_SET); - ftruncate(request->local_fileno, 0); - } - } - - slot = get_active_slot(); + slot = obj_req->slot; slot->callback_func = process_response; slot->callback_data = request; request->slot = slot; - - curl_easy_setopt(slot->curl, CURLOPT_FILE, request); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); - curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); - - /* If we have successfully processed data from a previous fetch - attempt, only fetch the data we don't already have. */ - if (prev_posn>0) { - if (push_verbosely) - fprintf(stderr, - "Resuming fetch of object %s at byte %ld\n", - hex, prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(slot->curl, - CURLOPT_HTTPHEADER, range_header); - } + request->userData = obj_req; /* Try to get the request started, abort the request on error */ request->state = RUN_FETCH_LOOSE; if (!start_active_slot(slot)) { fprintf(stderr, "Unable to start GET request\n"); - remote->can_update_info_refs = 0; + repo->can_update_info_refs = 0; + release_http_object_request(obj_req); release_request(request); } } @@ -399,7 +269,7 @@ static void start_mkcol(struct transfer_request *request) char *hex = sha1_to_hex(request->obj->sha1); struct active_request_slot *slot; - request->url = get_remote_object_url(remote->url, hex, 1); + request->url = get_remote_object_url(repo->url, hex, 1); slot = get_active_slot(); slot->callback_func = process_response; @@ -423,21 +293,15 @@ static void start_mkcol(struct transfer_request *request) static void start_fetch_packed(struct transfer_request *request) { - char *url; struct packed_git *target; - FILE *packfile; - char *filename; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; struct transfer_request *check_request = request_queue_head; - struct active_request_slot *slot; + struct http_pack_request *preq; - target = find_sha1_pack(request->obj->sha1, remote->packs); + target = find_sha1_pack(request->obj->sha1, repo->packs); if (!target) { fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", sha1_to_hex(request->obj->sha1)); - remote->can_update_info_refs = 0; + repo->can_update_info_refs = 0; release_request(request); return; } @@ -445,67 +309,36 @@ static void start_fetch_packed(struct transfer_request *request) fprintf(stderr, "Fetching pack %s\n", sha1_to_hex(target->sha1)); fprintf(stderr, " which contains %s\n", sha1_to_hex(request->obj->sha1)); - filename = sha1_pack_name(target->sha1); - snprintf(request->filename, sizeof(request->filename), "%s", filename); - snprintf(request->tmpfile, sizeof(request->tmpfile), - "%s.temp", filename); - - url = xmalloc(strlen(remote->url) + 64); - sprintf(url, "%sobjects/pack/pack-%s.pack", - remote->url, sha1_to_hex(target->sha1)); + preq = new_http_pack_request(target, repo->url); + if (preq == NULL) { + release_http_pack_request(preq); + repo->can_update_info_refs = 0; + return; + } + preq->lst = &repo->packs; /* Make sure there isn't another open request for this pack */ while (check_request) { if (check_request->state == RUN_FETCH_PACKED && - !strcmp(check_request->url, url)) { - free(url); + !strcmp(check_request->url, preq->url)) { + release_http_pack_request(preq); release_request(request); return; } check_request = check_request->next; } - packfile = fopen(request->tmpfile, "a"); - if (!packfile) { - fprintf(stderr, "Unable to open local file %s for pack", - request->tmpfile); - remote->can_update_info_refs = 0; - free(url); - return; - } - - slot = get_active_slot(); - slot->callback_func = process_response; - slot->callback_data = request; - request->slot = slot; - request->local_stream = packfile; - request->userData = target; - - request->url = url; - curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); - slot->local = packfile; - - /* If there is data present from a previous transfer attempt, - resume where it left off */ - prev_posn = ftell(packfile); - if (prev_posn>0) { - if (push_verbosely) - fprintf(stderr, - "Resuming fetch of pack %s at byte %ld\n", - sha1_to_hex(target->sha1), prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header); - } + preq->slot->callback_func = process_response; + preq->slot->callback_data = request; + request->slot = preq->slot; + request->userData = preq; /* Try to get the request started, abort the request on error */ request->state = RUN_FETCH_PACKED; - if (!start_active_slot(slot)) { + if (!start_active_slot(preq->slot)) { fprintf(stderr, "Unable to start GET request\n"); - remote->can_update_info_refs = 0; + release_http_pack_request(preq); + repo->can_update_info_refs = 0; release_request(request); } } @@ -554,10 +387,10 @@ static void start_put(struct transfer_request *request) request->buffer.buf.len = stream.total_out; strbuf_addstr(&buf, "Destination: "); - append_remote_object_url(&buf, remote->url, hex, 0); + append_remote_object_url(&buf, repo->url, hex, 0); request->dest = strbuf_detach(&buf, NULL); - append_remote_object_url(&buf, remote->url, hex, 0); + append_remote_object_url(&buf, repo->url, hex, 0); strbuf_add(&buf, request->lock->tmpfile_suffix, 41); request->url = strbuf_detach(&buf, NULL); @@ -567,6 +400,10 @@ static void start_put(struct transfer_request *request) curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer); +#endif curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); @@ -648,7 +485,7 @@ static int refresh_lock(struct remote_lock *lock) static void check_locks(void) { - struct remote_lock *lock = remote->locks; + struct remote_lock *lock = repo->locks; time_t current_time = time(NULL); int time_remaining; @@ -681,19 +518,14 @@ static void release_request(struct transfer_request *request) entry->next = entry->next->next; } - if (request->local_fileno != -1) - close(request->local_fileno); - if (request->local_stream) - fclose(request->local_stream); free(request->url); free(request); } static void finish_request(struct transfer_request *request) { - struct stat st; - struct packed_git *target; - struct packed_git **lst; + struct http_pack_request *preq; + struct http_object_request *obj_req; request->curl_result = request->slot->curl_result; request->http_code = request->slot->http_code; @@ -748,76 +580,46 @@ static void finish_request(struct transfer_request *request) aborted = 1; } } else if (request->state == RUN_FETCH_LOOSE) { - close(request->local_fileno); request->local_fileno = -1; + obj_req = (struct http_object_request *)request->userData; - if (request->curl_result != CURLE_OK && - request->http_code != 416) { - if (stat(request->tmpfile, &st) == 0) { - if (st.st_size == 0) - unlink(request->tmpfile); - } - } else { - if (request->http_code == 416) - fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); - - git_inflate_end(&request->stream); - git_SHA1_Final(request->real_sha1, &request->c); - if (request->zret != Z_STREAM_END) { - unlink(request->tmpfile); - } else if (hashcmp(request->obj->sha1, request->real_sha1)) { - unlink(request->tmpfile); - } else { - request->rename = - move_temp_to_file( - request->tmpfile, - request->filename); - if (request->rename == 0) { - request->obj->flags |= (LOCAL | REMOTE); - } - } - } + if (finish_http_object_request(obj_req) == 0) + if (obj_req->rename == 0) + request->obj->flags |= (LOCAL | REMOTE); /* Try fetching packed if necessary */ - if (request->obj->flags & LOCAL) + if (request->obj->flags & LOCAL) { + release_http_object_request(obj_req); release_request(request); - else + } else start_fetch_packed(request); } else if (request->state == RUN_FETCH_PACKED) { + int fail = 1; if (request->curl_result != CURLE_OK) { fprintf(stderr, "Unable to get pack file %s\n%s", request->url, curl_errorstr); - remote->can_update_info_refs = 0; } else { - off_t pack_size = ftell(request->local_stream); - - fclose(request->local_stream); - request->local_stream = NULL; - if (!move_temp_to_file(request->tmpfile, - request->filename)) { - target = (struct packed_git *)request->userData; - target->pack_size = pack_size; - lst = &remote->packs; - while (*lst != target) - lst = &((*lst)->next); - *lst = (*lst)->next; - - if (!verify_pack(target)) - install_packed_git(target); - else - remote->can_update_info_refs = 0; + preq = (struct http_pack_request *)request->userData; + + if (preq) { + if (finish_http_pack_request(preq) > 0) + fail = 0; + release_http_pack_request(preq); } } + if (fail) + repo->can_update_info_refs = 0; release_request(request); } } #ifdef USE_CURL_MULTI +static int is_running_queue; static int fill_active_slot(void *unused) { - struct transfer_request *request = request_queue_head; + struct transfer_request *request; - if (aborted) + if (aborted || !is_running_queue) return 0; for (request = request_queue_head; request; request = request->next) { @@ -860,8 +662,6 @@ static void add_fetch_request(struct object *obj) request->url = NULL; request->lock = NULL; request->headers = NULL; - request->local_fileno = -1; - request->local_stream = NULL; request->state = NEED_FETCH; request->next = request_queue_head; request_queue_head = request; @@ -888,7 +688,7 @@ static int add_send_request(struct object *obj, struct remote_lock *lock) get_remote_object_list(obj->sha1[0]); if (obj->flags & (REMOTE | PUSHING)) return 0; - target = find_sha1_pack(obj->sha1, remote->packs); + target = find_sha1_pack(obj->sha1, repo->packs); if (target) { obj->flags |= REMOTE; return 0; @@ -900,8 +700,6 @@ static int add_send_request(struct object *obj, struct remote_lock *lock) request->url = NULL; request->lock = lock; request->headers = NULL; - request->local_fileno = -1; - request->local_stream = NULL; request->state = NEED_PUSH; request->next = request_queue_head; request_queue_head = request; @@ -914,176 +712,23 @@ static int add_send_request(struct object *obj, struct remote_lock *lock) return 1; } -static int fetch_index(unsigned char *sha1) -{ - char *hex = sha1_to_hex(sha1); - char *filename; - char *url; - char tmpfile[PATH_MAX]; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; - - FILE *indexfile; - struct active_request_slot *slot; - struct slot_results results; - - /* Don't use the index if the pack isn't there */ - url = xmalloc(strlen(remote->url) + 64); - sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex); - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - free(url); - return error("Unable to verify pack %s is available", - hex); - } - } else { - free(url); - return error("Unable to start request"); - } - - if (has_pack_index(sha1)) { - free(url); - return 0; - } - - if (push_verbosely) - fprintf(stderr, "Getting index for pack %s\n", hex); - - sprintf(url, "%sobjects/pack/pack-%s.idx", remote->url, hex); - - filename = sha1_pack_index_name(sha1); - snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); - indexfile = fopen(tmpfile, "a"); - if (!indexfile) { - free(url); - return error("Unable to open local file %s for pack index", - tmpfile); - } - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); - slot->local = indexfile; - - /* If there is data present from a previous transfer attempt, - resume where it left off */ - prev_posn = ftell(indexfile); - if (prev_posn>0) { - if (push_verbosely) - fprintf(stderr, - "Resuming fetch of index for pack %s at byte %ld\n", - hex, prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header); - } - - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - free(url); - fclose(indexfile); - return error("Unable to get pack index %s\n%s", url, - curl_errorstr); - } - } else { - free(url); - fclose(indexfile); - return error("Unable to start request"); - } - - free(url); - fclose(indexfile); - - return move_temp_to_file(tmpfile, filename); -} - -static int setup_index(unsigned char *sha1) -{ - struct packed_git *new_pack; - - if (fetch_index(sha1)) - return -1; - - new_pack = parse_pack_index(sha1); - new_pack->next = remote->packs; - remote->packs = new_pack; - return 0; -} - static int fetch_indices(void) { - unsigned char sha1[20]; - char *url; - struct strbuf buffer = STRBUF_INIT; - char *data; - int i = 0; - - struct active_request_slot *slot; - struct slot_results results; + int ret; if (push_verbosely) fprintf(stderr, "Getting pack list\n"); - url = xmalloc(strlen(remote->url) + 20); - sprintf(url, "%sobjects/info/packs", remote->url); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - strbuf_release(&buffer); - free(url); - if (results.http_code == 404) - return 0; - else - return error("%s", curl_errorstr); - } - } else { - strbuf_release(&buffer); - free(url); - return error("Unable to start request"); - } - free(url); - - data = buffer.buf; - while (i < buffer.len) { - switch (data[i]) { - case 'P': - i++; - if (i + 52 < buffer.len && - !prefixcmp(data + i, " pack-") && - !prefixcmp(data + i + 46, ".pack\n")) { - get_sha1_hex(data + i + 6, sha1); - setup_index(sha1); - i += 51; - break; - } - default: - while (data[i] != '\n') - i++; - } - i++; + switch (http_get_info_packs(repo->url, &repo->packs)) { + case HTTP_OK: + case HTTP_MISSING_TARGET: + ret = 0; + break; + default: + ret = -1; } - strbuf_release(&buffer); - return 0; + return ret; } static void one_remote_object(const char *hex) @@ -1221,12 +866,13 @@ static struct remote_lock *lock_remote(const char *path, long timeout) struct remote_lock *lock = NULL; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; + char *escaped; - url = xmalloc(strlen(remote->url) + strlen(path) + 1); - sprintf(url, "%s%s", remote->url, path); + url = xmalloc(strlen(repo->url) + strlen(path) + 1); + sprintf(url, "%s%s", repo->url, path); /* Make sure leading directories exist for the remote ref */ - ep = strchr(url + strlen(remote->url) + 1, '/'); + ep = strchr(url + strlen(repo->url) + 1, '/'); while (ep) { char saved_character = ep[1]; ep[1] = '\0'; @@ -1255,7 +901,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout) ep = strchr(ep + 1, '/'); } - strbuf_addf(&out_buffer.buf, LOCK_REQUEST, git_default_email); + escaped = xml_entities(git_default_email); + strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped); + free(escaped); sprintf(timeout_header, "Timeout: Second-%ld", timeout); dav_headers = curl_slist_append(dav_headers, timeout_header); @@ -1266,6 +914,10 @@ static struct remote_lock *lock_remote(const char *path, long timeout) curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); +#endif curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); @@ -1318,8 +970,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout) } else { lock->url = url; lock->start_time = time(NULL); - lock->next = remote->locks; - remote->locks = lock; + lock->next = repo->locks; + repo->locks = lock; } return lock; @@ -1329,7 +981,7 @@ static int unlock_remote(struct remote_lock *lock) { struct active_request_slot *slot; struct slot_results results; - struct remote_lock *prev = remote->locks; + struct remote_lock *prev = repo->locks; struct curl_slist *dav_headers; int rc = 0; @@ -1355,8 +1007,8 @@ static int unlock_remote(struct remote_lock *lock) curl_slist_free_all(dav_headers); - if (remote->locks == lock) { - remote->locks = lock->next; + if (repo->locks == lock) { + repo->locks = lock->next; } else { while (prev && prev->next != lock) prev = prev->next; @@ -1374,7 +1026,7 @@ static int unlock_remote(struct remote_lock *lock) static void remove_locks(void) { - struct remote_lock *lock = remote->locks; + struct remote_lock *lock = repo->locks; fprintf(stderr, "Removing remote locks...\n"); while (lock) { @@ -1457,7 +1109,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) } } if (path) { - path += remote->path_len; + path += repo->path_len; ls->dentry_name = xstrdup(path); } } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { @@ -1480,7 +1132,7 @@ static void remote_ls(const char *path, int flags, void (*userFunc)(struct remote_ls_ctx *ls), void *userData) { - char *url = xmalloc(strlen(remote->url) + strlen(path) + 1); + char *url = xmalloc(strlen(repo->url) + strlen(path) + 1); struct active_request_slot *slot; struct slot_results results; struct strbuf in_buffer = STRBUF_INIT; @@ -1496,7 +1148,7 @@ static void remote_ls(const char *path, int flags, ls.userData = userData; ls.userFunc = userFunc; - sprintf(url, "%s%s", remote->url, path); + sprintf(url, "%s%s", repo->url, path); strbuf_addf(&out_buffer.buf, PROPFIND_ALL_REQUEST); @@ -1508,6 +1160,10 @@ static void remote_ls(const char *path, int flags, curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); +#endif curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); @@ -1573,8 +1229,11 @@ static int locking_available(void) struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; int lock_flags = 0; + char *escaped; - strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url); + escaped = xml_entities(repo->url); + strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, escaped); + free(escaped); dav_headers = curl_slist_append(dav_headers, "Depth: 0"); dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); @@ -1584,9 +1243,13 @@ static int locking_available(void) curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); +#endif curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url); + curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url); curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); @@ -1616,16 +1279,16 @@ static int locking_available(void) } XML_ParserFree(parser); if (!lock_flags) - error("Error: no DAV locking support on %s", - remote->url); + error("no DAV locking support on %s", + repo->url); } else { error("Cannot access URL %s, return code %d", - remote->url, results.curl_result); + repo->url, results.curl_result); lock_flags = 0; } } else { - error("Unable to start PROPFIND request on %s", remote->url); + error("Unable to start PROPFIND request on %s", repo->url); } strbuf_release(&out_buffer.buf); @@ -1766,6 +1429,10 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer); +#endif curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); @@ -1792,20 +1459,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) return 1; } -static struct ref *local_refs, **local_tail; -static struct ref *remote_refs, **remote_tail; - -static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) -{ - struct ref *ref; - int len = strlen(refname) + 1; - ref = xcalloc(1, sizeof(*ref) + len); - hashcpy(ref->new_sha1, sha1); - memcpy(ref->name, refname, len); - *local_tail = ref; - local_tail = &ref->next; - return 0; -} +static struct ref *remote_refs; static void one_remote_ref(char *refname) { @@ -1814,10 +1468,10 @@ static void one_remote_ref(char *refname) ref = alloc_ref(refname); - if (http_fetch_ref(remote->url, ref) != 0) { + if (http_fetch_ref(repo->url, ref) != 0) { fprintf(stderr, "Unable to fetch ref %s from %s\n", - refname, remote->url); + refname, repo->url); free(ref); return; } @@ -1826,7 +1480,7 @@ static void one_remote_ref(char *refname) * Fetch a copy of the object if it doesn't exist locally - it * may be required for updating server info later. */ - if (remote->can_update_info_refs && !has_sha1_file(ref->old_sha1)) { + if (repo->can_update_info_refs && !has_sha1_file(ref->old_sha1)) { obj = lookup_unknown_object(ref->old_sha1); if (obj) { fprintf(stderr, " fetch %s for %s\n", @@ -1835,82 +1489,15 @@ static void one_remote_ref(char *refname) } } - *remote_tail = ref; - remote_tail = &ref->next; -} - -static void get_local_heads(void) -{ - local_tail = &local_refs; - for_each_ref(one_local_ref, NULL); + ref->next = remote_refs; + remote_refs = ref; } static void get_dav_remote_heads(void) { - remote_tail = &remote_refs; remote_ls("refs/", (PROCESS_FILES | PROCESS_DIRS | RECURSIVE), process_ls_ref, NULL); } -static int is_zero_sha1(const unsigned char *sha1) -{ - int i; - - for (i = 0; i < 20; i++) { - if (*sha1++) - return 0; - } - return 1; -} - -static void unmark_and_free(struct commit_list *list, unsigned int mark) -{ - while (list) { - struct commit_list *temp = list; - temp->item->object.flags &= ~mark; - list = temp->next; - free(temp); - } -} - -static int ref_newer(const unsigned char *new_sha1, - const unsigned char *old_sha1) -{ - struct object *o; - struct commit *old, *new; - struct commit_list *list, *used; - int found = 0; - - /* Both new and old must be commit-ish and new is descendant of - * old. Otherwise we require --force. - */ - o = deref_tag(parse_object(old_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - old = (struct commit *) o; - - o = deref_tag(parse_object(new_sha1), NULL, 0); - if (!o || o->type != OBJ_COMMIT) - return 0; - new = (struct commit *) o; - - if (parse_commit(new) < 0) - return 0; - - used = list = NULL; - commit_list_insert(new, &list); - while (list) { - new = pop_most_recent_commit(&list, TMP_MARK); - commit_list_insert(new, &used); - if (new == old) { - found = 1; - break; - } - } - unmark_and_free(list, TMP_MARK); - unmark_and_free(used, TMP_MARK); - return found; -} - static void add_remote_info_ref(struct remote_ls_ctx *ls) { struct strbuf *buf = (struct strbuf *)ls->userData; @@ -1921,10 +1508,10 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls) ref = alloc_ref(ls->dentry_name); - if (http_fetch_ref(remote->url, ref) != 0) { + if (http_fetch_ref(repo->url, ref) != 0) { fprintf(stderr, "Unable to fetch ref %s from %s\n", - ls->dentry_name, remote->url); + ls->dentry_name, repo->url); aborted = 1; free(ref); return; @@ -1978,6 +1565,10 @@ static void update_remote_info_refs(struct remote_lock *lock) curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); +#ifndef NO_CURL_IOCTL + curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer); + curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer); +#endif curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); @@ -1999,30 +1590,23 @@ static void update_remote_info_refs(struct remote_lock *lock) static int remote_exists(const char *path) { - char *url = xmalloc(strlen(remote->url) + strlen(path) + 1); - struct active_request_slot *slot; - struct slot_results results; - int ret = -1; + char *url = xmalloc(strlen(repo->url) + strlen(path) + 1); + int ret; - sprintf(url, "%s%s", remote->url, path); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); + sprintf(url, "%s%s", repo->url, path); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.http_code == 404) - ret = 0; - else if (results.curl_result == CURLE_OK) - ret = 1; - else - fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code); - } else { - fprintf(stderr, "Unable to start HEAD request\n"); + switch (http_get_strbuf(url, NULL, 0)) { + case HTTP_OK: + ret = 1; + break; + case HTTP_MISSING_TARGET: + ret = 0; + break; + case HTTP_ERROR: + http_error(url, HTTP_ERROR); + default: + ret = -1; } - free(url); return ret; } @@ -2031,27 +1615,13 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) { char *url; struct strbuf buffer = STRBUF_INIT; - struct active_request_slot *slot; - struct slot_results results; - url = xmalloc(strlen(remote->url) + strlen(path) + 1); - sprintf(url, "%s%s", remote->url, path); + url = xmalloc(strlen(repo->url) + strlen(path) + 1); + sprintf(url, "%s%s", repo->url, path); - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - die("Couldn't get %s for remote symref\n%s", - url, curl_errorstr); - } - } else { - die("Unable to start remote symref request"); - } + if (http_get_strbuf(url, &buffer, 0) != HTTP_OK) + die("Couldn't get %s for remote symref\n%s", url, + curl_errorstr); free(url); free(*symref); @@ -2132,13 +1702,13 @@ static int delete_remote_branch(char *pattern, int force) /* Remote HEAD must resolve to a known object */ if (symref) return error("Remote HEAD symrefs too deep"); - if (is_zero_sha1(head_sha1)) + if (is_null_sha1(head_sha1)) return error("Unable to resolve remote HEAD"); if (!has_sha1_file(head_sha1)) return error("Remote HEAD resolves to object %s\nwhich does not exist locally, perhaps you need to fetch?", sha1_to_hex(head_sha1)); /* Remote branch must resolve to a known object */ - if (is_zero_sha1(remote_ref->old_sha1)) + if (is_null_sha1(remote_ref->old_sha1)) return error("Unable to resolve remote branch %s", remote_ref->name); if (!has_sha1_file(remote_ref->old_sha1)) @@ -2150,7 +1720,7 @@ static int delete_remote_branch(char *pattern, int force) "of your current HEAD.\n" "If you are sure you want to delete it," " run:\n\t'git http-push -D %s %s'", - remote_ref->name, remote->url, pattern); + remote_ref->name, repo->url, pattern); } } @@ -2158,8 +1728,8 @@ static int delete_remote_branch(char *pattern, int force) fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name); if (dry_run) return 0; - url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1); - sprintf(url, "%s%s", remote->url, remote_ref->name); + url = xmalloc(strlen(repo->url) + strlen(remote_ref->name) + 1); + sprintf(url, "%s%s", repo->url, remote_ref->name); slot = get_active_slot(); slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); @@ -2180,6 +1750,25 @@ static int delete_remote_branch(char *pattern, int force) return 0; } +void run_request_queue(void) +{ +#ifdef USE_CURL_MULTI + is_running_queue = 1; + fill_active_slots(); + add_fill_function(NULL, fill_active_slot); +#endif + do { + finish_all_active_slots(); +#ifdef USE_CURL_MULTI + fill_active_slots(); +#endif + } while (request_queue_head && !aborted); + +#ifdef USE_CURL_MULTI + is_running_queue = 0; +#endif +} + int main(int argc, char **argv) { struct transfer_request *request; @@ -2195,14 +1784,15 @@ int main(int argc, char **argv) int rc = 0; int i; int new_refs; - struct ref *ref; + struct ref *ref, *local_refs; + struct remote *remote; char *rewritten_url = NULL; git_extract_argv0_path(argv[0]); setup_git_directory(); - remote = xcalloc(sizeof(*remote), 1); + repo = xcalloc(sizeof(*repo), 1); argv++; for (i = 1; i < argc; i++, argv++) { @@ -2223,6 +1813,7 @@ int main(int argc, char **argv) } if (!strcmp(arg, "--verbose")) { push_verbosely = 1; + http_is_verbose = 1; continue; } if (!strcmp(arg, "-d")) { @@ -2235,14 +1826,14 @@ int main(int argc, char **argv) continue; } } - if (!remote->url) { + if (!repo->url) { char *path = strstr(arg, "//"); - remote->url = arg; - remote->path_len = strlen(arg); + repo->url = arg; + repo->path_len = strlen(arg); if (path) { - remote->path = strchr(path+2, '/'); - if (remote->path) - remote->path_len = strlen(remote->path); + repo->path = strchr(path+2, '/'); + if (repo->path) + repo->path_len = strlen(repo->path); } continue; } @@ -2255,7 +1846,7 @@ int main(int argc, char **argv) die("git-push is not available for http/https repository when not compiled with USE_CURL_MULTI"); #endif - if (!remote->url) + if (!repo->url) usage(http_push_usage); if (delete_branch && nr_refspec != 1) @@ -2263,19 +1854,28 @@ int main(int argc, char **argv) memset(remote_dir_exists, -1, 256); - http_init(NULL); - - no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); + /* + * Create a minimum remote by hand to give to http_init(), + * primarily to allow it to look at the URL. + */ + remote = xcalloc(sizeof(*remote), 1); + ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc); + remote->url[remote->url_nr++] = repo->url; + http_init(remote); - if (remote->url && remote->url[strlen(remote->url)-1] != '/') { - rewritten_url = xmalloc(strlen(remote->url)+2); - strcpy(rewritten_url, remote->url); + if (repo->url && repo->url[strlen(repo->url)-1] != '/') { + rewritten_url = xmalloc(strlen(repo->url)+2); + strcpy(rewritten_url, repo->url); strcat(rewritten_url, "/"); - remote->path = rewritten_url + (remote->path - remote->url); - remote->path_len++; - remote->url = rewritten_url; + repo->path = rewritten_url + (repo->path - repo->url); + repo->path_len++; + repo->url = rewritten_url; } +#ifdef USE_CURL_MULTI + is_running_queue = 0; +#endif + /* Verify DAV compliance/lock support */ if (!locking_available()) { rc = 1; @@ -2285,26 +1885,27 @@ int main(int argc, char **argv) sigchain_push_common(remove_locks_on_signal); /* Check whether the remote has server info files */ - remote->can_update_info_refs = 0; - remote->has_info_refs = remote_exists("info/refs"); - remote->has_info_packs = remote_exists("objects/info/packs"); - if (remote->has_info_refs) { + repo->can_update_info_refs = 0; + repo->has_info_refs = remote_exists("info/refs"); + repo->has_info_packs = remote_exists("objects/info/packs"); + if (repo->has_info_refs) { info_ref_lock = lock_remote("info/refs", LOCK_TIME); if (info_ref_lock) - remote->can_update_info_refs = 1; + repo->can_update_info_refs = 1; else { - fprintf(stderr, "Error: cannot lock existing info/refs\n"); + error("cannot lock existing info/refs"); rc = 1; goto cleanup; } } - if (remote->has_info_packs) + if (repo->has_info_packs) fetch_indices(); /* Get a list of all local and remote heads to validate refspecs */ - get_local_heads(); + local_refs = get_local_heads(); fprintf(stderr, "Fetching remote heads...\n"); get_dav_remote_heads(); + run_request_queue(); /* Remove a remote branch if -d or -D was specified */ if (delete_branch) { @@ -2315,9 +1916,7 @@ int main(int argc, char **argv) } /* match them up */ - if (!remote_tail) - remote_tail = &remote_refs; - if (match_refs(local_refs, remote_refs, &remote_tail, + if (match_refs(local_refs, &remote_refs, nr_refspec, (const char **) refspec, push_all)) { rc = -1; goto cleanup; @@ -2331,14 +1930,14 @@ int main(int argc, char **argv) new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { char old_hex[60], *new_hex; - const char *commit_argv[4]; + const char *commit_argv[5]; int commit_argc; char *new_sha1_hex, *old_sha1_hex; if (!ref->peer_ref) continue; - if (is_zero_sha1(ref->peer_ref->new_sha1)) { + if (is_null_sha1(ref->peer_ref->new_sha1)) { if (delete_remote_branch(ref->name, 1) == -1) { error("Could not remove %s", ref->name); rc = -4; @@ -2354,7 +1953,7 @@ int main(int argc, char **argv) } if (!force_all && - !is_zero_sha1(ref->old_sha1) && + !is_null_sha1(ref->old_sha1) && !ref->force) { if (!has_sha1_file(ref->old_sha1) || !ref_newer(ref->peer_ref->new_sha1, @@ -2404,13 +2003,14 @@ int main(int argc, char **argv) old_sha1_hex = NULL; commit_argv[1] = "--objects"; commit_argv[2] = new_sha1_hex; - if (!push_all && !is_zero_sha1(ref->old_sha1)) { + if (!push_all && !is_null_sha1(ref->old_sha1)) { old_sha1_hex = xmalloc(42); sprintf(old_sha1_hex, "^%s", sha1_to_hex(ref->old_sha1)); commit_argv[3] = old_sha1_hex; commit_argc++; } + commit_argv[commit_argc] = NULL; init_revisions(&revs, setup_git_directory()); setup_revisions(commit_argc, commit_argv, &revs, NULL); revs.edge_hint = 0; /* just in case */ @@ -2434,16 +2034,8 @@ int main(int argc, char **argv) if (objects_to_send) fprintf(stderr, " sending %d objects\n", objects_to_send); -#ifdef USE_CURL_MULTI - fill_active_slots(); - add_fill_function(NULL, fill_active_slot); -#endif - do { - finish_all_active_slots(); -#ifdef USE_CURL_MULTI - fill_active_slots(); -#endif - } while (request_queue_head && !aborted); + + run_request_queue(); /* Update the remote branch if all went well */ if (aborted || !update_remote(ref->new_sha1, ref_lock)) @@ -2456,8 +2048,8 @@ int main(int argc, char **argv) } /* Update remote server info if appropriate */ - if (remote->has_info_refs && new_refs) { - if (info_ref_lock && remote->can_update_info_refs) { + if (repo->has_info_refs && new_refs) { + if (info_ref_lock && repo->can_update_info_refs) { fprintf(stderr, "Updating remote server info\n"); if (!dry_run) update_remote_info_refs(info_ref_lock); @@ -2470,9 +2062,7 @@ int main(int argc, char **argv) free(rewritten_url); if (info_ref_lock) unlock_remote(info_ref_lock); - free(remote); - - curl_slist_free_all(no_pragma_header); + free(repo); http_cleanup(); diff --git a/http-walker.c b/http-walker.c index c5a3ea3b31..700bc13112 100644 --- a/http-walker.c +++ b/http-walker.c @@ -1,12 +1,8 @@ #include "cache.h" #include "commit.h" -#include "pack.h" #include "walker.h" #include "http.h" -#define PREV_BUF_SIZE 4096 -#define RANGE_HEADER_SIZE 30 - struct alt_base { char *base; @@ -27,20 +23,8 @@ struct object_request struct walker *walker; unsigned char sha1[20]; struct alt_base *repo; - char *url; - char filename[PATH_MAX]; - char tmpfile[PATH_MAX]; - int local; enum object_request_state state; - CURLcode curl_result; - char errorstr[CURL_ERROR_SIZE]; - long http_code; - unsigned char real_sha1[20]; - git_SHA_CTX c; - z_stream stream; - int zret; - int rename; - struct active_request_slot *slot; + struct http_object_request *req; struct object_request *next; }; @@ -57,39 +41,10 @@ struct walker_data { const char *url; int got_alternates; struct alt_base *alt; - struct curl_slist *no_pragma_header; }; static struct object_request *object_queue_head; -static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, - void *data) -{ - unsigned char expn[4096]; - size_t size = eltsize * nmemb; - int posn = 0; - struct object_request *obj_req = (struct object_request *)data; - do { - ssize_t retval = xwrite(obj_req->local, - (char *) ptr + posn, size - posn); - if (retval < 0) - return posn; - posn += retval; - } while (posn < size); - - obj_req->stream.avail_in = size; - obj_req->stream.next_in = ptr; - do { - obj_req->stream.next_out = expn; - obj_req->stream.avail_out = sizeof(expn); - obj_req->zret = git_inflate(&obj_req->stream, Z_SYNC_FLUSH); - git_SHA1_Update(&obj_req->c, expn, - sizeof(expn) - obj_req->stream.avail_out); - } while (obj_req->stream.avail_in && obj_req->zret == Z_OK); - data_received++; - return size; -} - static void fetch_alternates(struct walker *walker, const char *base); static void process_object_response(void *callback_data); @@ -97,165 +52,35 @@ static void process_object_response(void *callback_data); static void start_object_request(struct walker *walker, struct object_request *obj_req) { - char *hex = sha1_to_hex(obj_req->sha1); - char prevfile[PATH_MAX]; - char *url; - char *posn; - int prevlocal; - unsigned char prev_buf[PREV_BUF_SIZE]; - ssize_t prev_read = 0; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; struct active_request_slot *slot; - struct walker_data *data = walker->data; + struct http_object_request *req; - snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename); - unlink(prevfile); - rename(obj_req->tmpfile, prevfile); - unlink(obj_req->tmpfile); - - if (obj_req->local != -1) - error("fd leakage in start: %d", obj_req->local); - obj_req->local = open(obj_req->tmpfile, - O_WRONLY | O_CREAT | O_EXCL, 0666); - /* This could have failed due to the "lazy directory creation"; - * try to mkdir the last path component. - */ - if (obj_req->local < 0 && errno == ENOENT) { - char *dir = strrchr(obj_req->tmpfile, '/'); - if (dir) { - *dir = 0; - mkdir(obj_req->tmpfile, 0777); - *dir = '/'; - } - obj_req->local = open(obj_req->tmpfile, - O_WRONLY | O_CREAT | O_EXCL, 0666); - } - - if (obj_req->local < 0) { + req = new_http_object_request(obj_req->repo->base, obj_req->sha1); + if (req == NULL) { obj_req->state = ABORTED; - error("Couldn't create temporary file %s for %s: %s", - obj_req->tmpfile, obj_req->filename, strerror(errno)); return; } + obj_req->req = req; - memset(&obj_req->stream, 0, sizeof(obj_req->stream)); - - git_inflate_init(&obj_req->stream); - - git_SHA1_Init(&obj_req->c); - - url = xmalloc(strlen(obj_req->repo->base) + 51); - obj_req->url = xmalloc(strlen(obj_req->repo->base) + 51); - strcpy(url, obj_req->repo->base); - posn = url + strlen(obj_req->repo->base); - strcpy(posn, "/objects/"); - posn += 9; - memcpy(posn, hex, 2); - posn += 2; - *(posn++) = '/'; - strcpy(posn, hex + 2); - strcpy(obj_req->url, url); - - /* If a previous temp file is present, process what was already - fetched. */ - prevlocal = open(prevfile, O_RDONLY); - if (prevlocal != -1) { - do { - prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE); - if (prev_read>0) { - if (fwrite_sha1_file(prev_buf, - 1, - prev_read, - obj_req) == prev_read) { - prev_posn += prev_read; - } else { - prev_read = -1; - } - } - } while (prev_read > 0); - close(prevlocal); - } - unlink(prevfile); - - /* Reset inflate/SHA1 if there was an error reading the previous temp - file; also rewind to the beginning of the local file. */ - if (prev_read == -1) { - memset(&obj_req->stream, 0, sizeof(obj_req->stream)); - git_inflate_init(&obj_req->stream); - git_SHA1_Init(&obj_req->c); - if (prev_posn>0) { - prev_posn = 0; - lseek(obj_req->local, 0, SEEK_SET); - ftruncate(obj_req->local, 0); - } - } - - slot = get_active_slot(); + slot = req->slot; slot->callback_func = process_object_response; slot->callback_data = obj_req; - obj_req->slot = slot; - - curl_easy_setopt(slot->curl, CURLOPT_FILE, obj_req); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); - curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, obj_req->errorstr); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header); - - /* If we have successfully processed data from a previous fetch - attempt, only fetch the data we don't already have. */ - if (prev_posn>0) { - if (walker->get_verbosely) - fprintf(stderr, - "Resuming fetch of object %s at byte %ld\n", - hex, prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(slot->curl, - CURLOPT_HTTPHEADER, range_header); - } /* Try to get the request started, abort the request on error */ obj_req->state = ACTIVE; if (!start_active_slot(slot)) { obj_req->state = ABORTED; - obj_req->slot = NULL; - close(obj_req->local); obj_req->local = -1; - free(obj_req->url); + release_http_object_request(req); return; } } static void finish_object_request(struct object_request *obj_req) { - struct stat st; - - close(obj_req->local); obj_req->local = -1; - - if (obj_req->http_code == 416) { - fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); - } else if (obj_req->curl_result != CURLE_OK) { - if (stat(obj_req->tmpfile, &st) == 0) - if (st.st_size == 0) - unlink(obj_req->tmpfile); - return; - } - - git_inflate_end(&obj_req->stream); - git_SHA1_Final(obj_req->real_sha1, &obj_req->c); - if (obj_req->zret != Z_STREAM_END) { - unlink(obj_req->tmpfile); + if (finish_http_object_request(obj_req->req)) return; - } - if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { - unlink(obj_req->tmpfile); - return; - } - obj_req->rename = - move_temp_to_file(obj_req->tmpfile, obj_req->filename); - if (obj_req->rename == 0) + if (obj_req->req->rename == 0) walker_say(obj_req->walker, "got %s\n", sha1_to_hex(obj_req->sha1)); } @@ -267,19 +92,16 @@ static void process_object_response(void *callback_data) struct walker_data *data = walker->data; struct alt_base *alt = data->alt; - obj_req->curl_result = obj_req->slot->curl_result; - obj_req->http_code = obj_req->slot->http_code; - obj_req->slot = NULL; + process_http_object_request(obj_req->req); obj_req->state = COMPLETE; /* Use alternates if necessary */ - if (missing_target(obj_req)) { + if (missing_target(obj_req->req)) { fetch_alternates(walker, alt->base); if (obj_req->repo->next != NULL) { obj_req->repo = obj_req->repo->next; - close(obj_req->local); - obj_req->local = -1; + release_http_object_request(obj_req->req); start_object_request(walker, obj_req); return; } @@ -292,8 +114,8 @@ static void release_object_request(struct object_request *obj_req) { struct object_request *entry = object_queue_head; - if (obj_req->local != -1) - error("fd leakage in release: %d", obj_req->local); + if (obj_req->req !=NULL && obj_req->req->localfile != -1) + error("fd leakage in release: %d", obj_req->req->localfile); if (obj_req == object_queue_head) { object_queue_head = obj_req->next; } else { @@ -303,7 +125,6 @@ static void release_object_request(struct object_request *obj_req) entry->next = entry->next->next; } - free(obj_req->url); free(obj_req); } @@ -331,28 +152,23 @@ static void prefetch(struct walker *walker, unsigned char *sha1) struct object_request *newreq; struct object_request *tail; struct walker_data *data = walker->data; - char *filename = sha1_file_name(sha1); newreq = xmalloc(sizeof(*newreq)); newreq->walker = walker; hashcpy(newreq->sha1, sha1); newreq->repo = data->alt; - newreq->url = NULL; - newreq->local = -1; newreq->state = WAITING; - snprintf(newreq->filename, sizeof(newreq->filename), "%s", filename); - snprintf(newreq->tmpfile, sizeof(newreq->tmpfile), - "%s.temp", filename); - newreq->slot = NULL; + newreq->req = NULL; newreq->next = NULL; + http_is_verbose = walker->get_verbosely; + if (object_queue_head == NULL) { object_queue_head = newreq; } else { tail = object_queue_head; - while (tail->next != NULL) { + while (tail->next != NULL) tail = tail->next; - } tail->next = newreq; } @@ -362,92 +178,6 @@ static void prefetch(struct walker *walker, unsigned char *sha1) #endif } -static int fetch_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1) -{ - char *hex = sha1_to_hex(sha1); - char *filename; - char *url; - char tmpfile[PATH_MAX]; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; - struct walker_data *data = walker->data; - - FILE *indexfile; - struct active_request_slot *slot; - struct slot_results results; - - if (has_pack_index(sha1)) - return 0; - - if (walker->get_verbosely) - fprintf(stderr, "Getting index for pack %s\n", hex); - - url = xmalloc(strlen(repo->base) + 64); - sprintf(url, "%s/objects/pack/pack-%s.idx", repo->base, hex); - - filename = sha1_pack_index_name(sha1); - snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); - indexfile = fopen(tmpfile, "a"); - if (!indexfile) - return error("Unable to open local file %s for pack index", - tmpfile); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header); - slot->local = indexfile; - - /* If there is data present from a previous transfer attempt, - resume where it left off */ - prev_posn = ftell(indexfile); - if (prev_posn>0) { - if (walker->get_verbosely) - fprintf(stderr, - "Resuming fetch of index for pack %s at byte %ld\n", - hex, prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header); - } - - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - fclose(indexfile); - return error("Unable to get pack index %s\n%s", url, - curl_errorstr); - } - } else { - fclose(indexfile); - return error("Unable to start request"); - } - - fclose(indexfile); - - return move_temp_to_file(tmpfile, filename); -} - -static int setup_index(struct walker *walker, struct alt_base *repo, unsigned char *sha1) -{ - struct packed_git *new_pack; - if (has_pack_file(sha1)) - return 0; /* don't list this as something we can get */ - - if (fetch_index(walker, repo, sha1)) - return -1; - - new_pack = parse_pack_index(sha1); - if (!new_pack) - return -1; /* parse_pack_index() already issued error message */ - new_pack->next = repo->packs; - repo->packs = new_pack; - return 0; -} - static void process_alternates_response(void *callback_data) { struct alternates_request *alt_req = @@ -504,7 +234,8 @@ static void process_alternates_response(void *callback_data) struct alt_base *newalt; char *target = NULL; if (data[i] == '/') { - /* This counts + /* + * This counts * http://git.host/pub/scm/linux.git/ * -----------here^ * so memcpy(dst, base, serverlen) will @@ -517,7 +248,8 @@ static void process_alternates_response(void *callback_data) okay = 1; } } else if (!memcmp(data + i, "../", 3)) { - /* Relative URL; chop the corresponding + /* + * Relative URL; chop the corresponding * number of subpath from base (and ../ * from data), and concatenate the result. * @@ -546,7 +278,7 @@ static void process_alternates_response(void *callback_data) } /* If the server got removed, give up. */ okay = strchr(base, ':') - base + 3 < - serverlen; + serverlen; } else if (alt_req->http_specific) { char *colon = strchr(data + i, ':'); char *slash = strchr(data + i, '/'); @@ -590,9 +322,11 @@ static void fetch_alternates(struct walker *walker, const char *base) struct alternates_request alt_req; struct walker_data *cdata = walker->data; - /* If another request has already started fetching alternates, - wait for them to arrive and return to processing this request's - curl message */ + /* + * If another request has already started fetching alternates, + * wait for them to arrive and return to processing this request's + * curl message + */ #ifdef USE_CURL_MULTI while (cdata->got_alternates == 0) { step_active_slots(); @@ -612,8 +346,10 @@ static void fetch_alternates(struct walker *walker, const char *base) url = xmalloc(strlen(base) + 31); sprintf(url, "%s/objects/info/http-alternates", base); - /* Use a callback to process the result, since another request - may fail and need to have alternates loaded before continuing */ + /* + * Use a callback to process the result, since another request + * may fail and need to have alternates loaded before continuing + */ slot = get_active_slot(); slot->callback_func = process_alternates_response; alt_req.walker = walker; @@ -640,15 +376,7 @@ static void fetch_alternates(struct walker *walker, const char *base) static int fetch_indices(struct walker *walker, struct alt_base *repo) { - unsigned char sha1[20]; - char *url; - struct strbuf buffer = STRBUF_INIT; - char *data; - int i = 0; - int ret = 0; - - struct active_request_slot *slot; - struct slot_results results; + int ret; if (repo->got_indices) return 0; @@ -656,76 +384,26 @@ static int fetch_indices(struct walker *walker, struct alt_base *repo) if (walker->get_verbosely) fprintf(stderr, "Getting pack list for %s\n", repo->base); - url = xmalloc(strlen(repo->base) + 21); - sprintf(url, "%s/objects/info/packs", repo->base); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - if (missing_target(&results)) { - repo->got_indices = 1; - goto cleanup; - } else { - repo->got_indices = 0; - ret = error("%s", curl_errorstr); - goto cleanup; - } - } - } else { + switch (http_get_info_packs(repo->base, &repo->packs)) { + case HTTP_OK: + case HTTP_MISSING_TARGET: + repo->got_indices = 1; + ret = 0; + break; + default: repo->got_indices = 0; - ret = error("Unable to start request"); - goto cleanup; + ret = -1; } - data = buffer.buf; - while (i < buffer.len) { - switch (data[i]) { - case 'P': - i++; - if (i + 52 <= buffer.len && - !prefixcmp(data + i, " pack-") && - !prefixcmp(data + i + 46, ".pack\n")) { - get_sha1_hex(data + i + 6, sha1); - setup_index(walker, repo, sha1); - i += 51; - break; - } - default: - while (i < buffer.len && data[i] != '\n') - i++; - } - i++; - } - - repo->got_indices = 1; -cleanup: - strbuf_release(&buffer); - free(url); return ret; } static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned char *sha1) { - char *url; struct packed_git *target; - struct packed_git **lst; - FILE *packfile; - char *filename; - char tmpfile[PATH_MAX]; int ret; - long prev_posn = 0; - char range[RANGE_HEADER_SIZE]; - struct curl_slist *range_header = NULL; - struct walker_data *data = walker->data; - - struct active_request_slot *slot; struct slot_results results; + struct http_pack_request *preq; if (fetch_indices(walker, repo)) return -1; @@ -740,80 +418,37 @@ static int fetch_pack(struct walker *walker, struct alt_base *repo, unsigned cha sha1_to_hex(sha1)); } - url = xmalloc(strlen(repo->base) + 65); - sprintf(url, "%s/objects/pack/pack-%s.pack", - repo->base, sha1_to_hex(target->sha1)); + preq = new_http_pack_request(target, repo->base); + if (preq == NULL) + goto abort; + preq->lst = &repo->packs; + preq->slot->results = &results; - filename = sha1_pack_name(target->sha1); - snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); - packfile = fopen(tmpfile, "a"); - if (!packfile) - return error("Unable to open local file %s for pack", - tmpfile); - - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, data->no_pragma_header); - slot->local = packfile; - - /* If there is data present from a previous transfer attempt, - resume where it left off */ - prev_posn = ftell(packfile); - if (prev_posn>0) { - if (walker->get_verbosely) - fprintf(stderr, - "Resuming fetch of pack %s at byte %ld\n", - sha1_to_hex(target->sha1), prev_posn); - sprintf(range, "Range: bytes=%ld-", prev_posn); - range_header = curl_slist_append(range_header, range); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header); - } - - if (start_active_slot(slot)) { - run_active_slot(slot); + if (start_active_slot(preq->slot)) { + run_active_slot(preq->slot); if (results.curl_result != CURLE_OK) { - fclose(packfile); - return error("Unable to get pack file %s\n%s", url, - curl_errorstr); + error("Unable to get pack file %s\n%s", preq->url, + curl_errorstr); + goto abort; } } else { - fclose(packfile); - return error("Unable to start request"); + error("Unable to start request"); + goto abort; } - target->pack_size = ftell(packfile); - fclose(packfile); - - ret = move_temp_to_file(tmpfile, filename); + ret = finish_http_pack_request(preq); + release_http_pack_request(preq); if (ret) return ret; - lst = &repo->packs; - while (*lst != target) - lst = &((*lst)->next); - *lst = (*lst)->next; - - if (verify_pack(target)) - return -1; - install_packed_git(target); - return 0; + +abort: + return -1; } static void abort_object_request(struct object_request *obj_req) { - if (obj_req->local >= 0) { - close(obj_req->local); - obj_req->local = -1; - } - unlink(obj_req->tmpfile); - if (obj_req->slot) { - release_active_slot(obj_req->slot); - obj_req->slot = NULL; - } release_object_request(obj_req); } @@ -822,6 +457,7 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c char *hex = sha1_to_hex(sha1); int ret = 0; struct object_request *obj_req = object_queue_head; + struct http_object_request *req; while (obj_req != NULL && hashcmp(obj_req->sha1, sha1)) obj_req = obj_req->next; @@ -829,45 +465,55 @@ static int fetch_object(struct walker *walker, struct alt_base *repo, unsigned c return error("Couldn't find request for %s in the queue", hex); if (has_sha1_file(obj_req->sha1)) { + if (obj_req->req != NULL) + abort_http_object_request(obj_req->req); abort_object_request(obj_req); return 0; } #ifdef USE_CURL_MULTI - while (obj_req->state == WAITING) { + while (obj_req->state == WAITING) step_active_slots(); - } #else start_object_request(walker, obj_req); #endif - while (obj_req->state == ACTIVE) { - run_active_slot(obj_req->slot); - } - if (obj_req->local != -1) { - close(obj_req->local); obj_req->local = -1; + /* + * obj_req->req might change when fetching alternates in the callback + * process_object_response; therefore, the "shortcut" variable, req, + * is used only after we're done with slots. + */ + while (obj_req->state == ACTIVE) + run_active_slot(obj_req->req->slot); + + req = obj_req->req; + + if (req->localfile != -1) { + close(req->localfile); + req->localfile = -1; } if (obj_req->state == ABORTED) { ret = error("Request for %s aborted", hex); - } else if (obj_req->curl_result != CURLE_OK && - obj_req->http_code != 416) { - if (missing_target(obj_req)) + } else if (req->curl_result != CURLE_OK && + req->http_code != 416) { + if (missing_target(req)) ret = -1; /* Be silent, it is probably in a pack. */ else ret = error("%s (curl_result = %d, http_code = %ld, sha1 = %s)", - obj_req->errorstr, obj_req->curl_result, - obj_req->http_code, hex); - } else if (obj_req->zret != Z_STREAM_END) { + req->errorstr, req->curl_result, + req->http_code, hex); + } else if (req->zret != Z_STREAM_END) { walker->corrupt_object_found++; - ret = error("File %s (%s) corrupt", hex, obj_req->url); - } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { + ret = error("File %s (%s) corrupt", hex, req->url); + } else if (hashcmp(obj_req->sha1, req->real_sha1)) { ret = error("File %s has bad hash", hex); - } else if (obj_req->rename < 0) { + } else if (req->rename < 0) { ret = error("unable to write sha1 filename %s", - obj_req->filename); + req->filename); } + release_http_object_request(req); release_object_request(obj_req); return ret; } @@ -897,10 +543,7 @@ static int fetch_ref(struct walker *walker, struct ref *ref) static void cleanup(struct walker *walker) { - struct walker_data *data = walker->data; http_cleanup(); - - curl_slist_free_all(data->no_pragma_header); } struct walker *get_http_walker(const char *url, struct remote *remote) @@ -911,8 +554,6 @@ struct walker *get_http_walker(const char *url, struct remote *remote) http_init(remote); - data->no_pragma_header = curl_slist_append(NULL, "Pragma:"); - data->alt = xmalloc(sizeof(*data->alt)); data->alt->base = xmalloc(strlen(url) + 1); strcpy(data->alt->base, url); @@ -1,7 +1,9 @@ #include "http.h" +#include "pack.h" int data_received; -int active_requests = 0; +int active_requests; +int http_is_verbose; #ifdef USE_CURL_MULTI static int max_requests = -1; @@ -10,25 +12,31 @@ static CURLM *curlm; #ifndef NO_CURL_EASY_DUPHANDLE static CURL *curl_default; #endif + +#define PREV_BUF_SIZE 4096 +#define RANGE_HEADER_SIZE 30 + char curl_errorstr[CURL_ERROR_SIZE]; static int curl_ssl_verify = -1; -static const char *ssl_cert = NULL; -#if LIBCURL_VERSION_NUM >= 0x070902 -static const char *ssl_key = NULL; +static const char *ssl_cert; +#if LIBCURL_VERSION_NUM >= 0x070903 +static const char *ssl_key; #endif #if LIBCURL_VERSION_NUM >= 0x070908 -static const char *ssl_capath = NULL; +static const char *ssl_capath; #endif -static const char *ssl_cainfo = NULL; +static const char *ssl_cainfo; static long curl_low_speed_limit = -1; static long curl_low_speed_time = -1; -static int curl_ftp_no_epsv = 0; -static const char *curl_http_proxy = NULL; +static int curl_ftp_no_epsv; +static const char *curl_http_proxy; +static char *user_name, *user_pass; static struct curl_slist *pragma_header; +static struct curl_slist *no_pragma_header; -static struct active_request_slot *active_queue_head = NULL; +static struct active_request_slot *active_queue_head; size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) { @@ -43,6 +51,25 @@ size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_) return size; } +#ifndef NO_CURL_IOCTL +curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp) +{ + struct buffer *buffer = clientp; + + switch (cmd) { + case CURLIOCMD_NOP: + return CURLIOE_OK; + + case CURLIOCMD_RESTARTREAD: + buffer->posn = 0; + return CURLIOE_OK; + + default: + return CURLIOE_UNKNOWNCMD; + } +} +#endif + size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_) { size_t size = eltsize * nmemb; @@ -94,53 +121,33 @@ static void process_curl_messages(void) static int http_options(const char *var, const char *value, void *cb) { if (!strcmp("http.sslverify", var)) { - if (curl_ssl_verify == -1) { - curl_ssl_verify = git_config_bool(var, value); - } - return 0; - } - - if (!strcmp("http.sslcert", var)) { - if (ssl_cert == NULL) - return git_config_string(&ssl_cert, var, value); - return 0; - } -#if LIBCURL_VERSION_NUM >= 0x070902 - if (!strcmp("http.sslkey", var)) { - if (ssl_key == NULL) - return git_config_string(&ssl_key, var, value); + curl_ssl_verify = git_config_bool(var, value); return 0; } + if (!strcmp("http.sslcert", var)) + return git_config_string(&ssl_cert, var, value); +#if LIBCURL_VERSION_NUM >= 0x070903 + if (!strcmp("http.sslkey", var)) + return git_config_string(&ssl_key, var, value); #endif #if LIBCURL_VERSION_NUM >= 0x070908 - if (!strcmp("http.sslcapath", var)) { - if (ssl_capath == NULL) - return git_config_string(&ssl_capath, var, value); - return 0; - } + if (!strcmp("http.sslcapath", var)) + return git_config_string(&ssl_capath, var, value); #endif - if (!strcmp("http.sslcainfo", var)) { - if (ssl_cainfo == NULL) - return git_config_string(&ssl_cainfo, var, value); - return 0; - } - + if (!strcmp("http.sslcainfo", var)) + return git_config_string(&ssl_cainfo, var, value); #ifdef USE_CURL_MULTI if (!strcmp("http.maxrequests", var)) { - if (max_requests == -1) - max_requests = git_config_int(var, value); + max_requests = git_config_int(var, value); return 0; } #endif - if (!strcmp("http.lowspeedlimit", var)) { - if (curl_low_speed_limit == -1) - curl_low_speed_limit = (long)git_config_int(var, value); + curl_low_speed_limit = (long)git_config_int(var, value); return 0; } if (!strcmp("http.lowspeedtime", var)) { - if (curl_low_speed_time == -1) - curl_low_speed_time = (long)git_config_int(var, value); + curl_low_speed_time = (long)git_config_int(var, value); return 0; } @@ -148,19 +155,28 @@ static int http_options(const char *var, const char *value, void *cb) curl_ftp_no_epsv = git_config_bool(var, value); return 0; } - if (!strcmp("http.proxy", var)) { - if (curl_http_proxy == NULL) - return git_config_string(&curl_http_proxy, var, value); - return 0; - } + if (!strcmp("http.proxy", var)) + return git_config_string(&curl_http_proxy, var, value); /* Fall back on the default ones */ return git_default_config(var, value, cb); } -static CURL* get_curl_handle(void) +static void init_curl_http_auth(CURL *result) { - CURL* result = curl_easy_init(); + if (user_name) { + struct strbuf up = STRBUF_INIT; + if (!user_pass) + user_pass = xstrdup(getpass("Password: ")); + strbuf_addf(&up, "%s:%s", user_name, user_pass); + curl_easy_setopt(result, CURLOPT_USERPWD, + strbuf_detach(&up, NULL)); + } +} + +static CURL *get_curl_handle(void) +{ + CURL *result = curl_easy_init(); if (!curl_ssl_verify) { curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0); @@ -176,9 +192,11 @@ static CURL* get_curl_handle(void) curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL); #endif + init_curl_http_auth(result); + if (ssl_cert != NULL) curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert); -#if LIBCURL_VERSION_NUM >= 0x070902 +#if LIBCURL_VERSION_NUM >= 0x070903 if (ssl_key != NULL) curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key); #endif @@ -213,17 +231,69 @@ static CURL* get_curl_handle(void) return result; } +static void http_auth_init(const char *url) +{ + char *at, *colon, *cp, *slash; + int len; + + cp = strstr(url, "://"); + if (!cp) + return; + + /* + * Ok, the URL looks like "proto://something". Which one? + * "proto://<user>:<pass>@<host>/...", + * "proto://<user>@<host>/...", or just + * "proto://<host>/..."? + */ + cp += 3; + at = strchr(cp, '@'); + colon = strchr(cp, ':'); + slash = strchrnul(cp, '/'); + if (!at || slash <= at) + return; /* No credentials */ + if (!colon || at <= colon) { + /* Only username */ + len = at - cp; + user_name = xmalloc(len + 1); + memcpy(user_name, cp, len); + user_name[len] = '\0'; + user_pass = NULL; + } else { + len = colon - cp; + user_name = xmalloc(len + 1); + memcpy(user_name, cp, len); + user_name[len] = '\0'; + len = at - (colon + 1); + user_pass = xmalloc(len + 1); + memcpy(user_pass, colon + 1, len); + user_pass[len] = '\0'; + } +} + +static void set_from_env(const char **var, const char *envname) +{ + const char *val = getenv(envname); + if (val) + *var = val; +} + void http_init(struct remote *remote) { char *low_speed_limit; char *low_speed_time; + http_is_verbose = 0; + + git_config(http_options, NULL); + curl_global_init(CURL_GLOBAL_ALL); if (remote && remote->http_proxy) curl_http_proxy = xstrdup(remote->http_proxy); pragma_header = curl_slist_append(pragma_header, "Pragma: no-cache"); + no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:"); #ifdef USE_CURL_MULTI { @@ -242,14 +312,14 @@ void http_init(struct remote *remote) if (getenv("GIT_SSL_NO_VERIFY")) curl_ssl_verify = 0; - ssl_cert = getenv("GIT_SSL_CERT"); -#if LIBCURL_VERSION_NUM >= 0x070902 - ssl_key = getenv("GIT_SSL_KEY"); + set_from_env(&ssl_cert, "GIT_SSL_CERT"); +#if LIBCURL_VERSION_NUM >= 0x070903 + set_from_env(&ssl_key, "GIT_SSL_KEY"); #endif #if LIBCURL_VERSION_NUM >= 0x070908 - ssl_capath = getenv("GIT_SSL_CAPATH"); + set_from_env(&ssl_capath, "GIT_SSL_CAPATH"); #endif - ssl_cainfo = getenv("GIT_SSL_CAINFO"); + set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO"); low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT"); if (low_speed_limit != NULL) @@ -258,8 +328,6 @@ void http_init(struct remote *remote) if (low_speed_time != NULL) curl_low_speed_time = strtol(low_speed_time, NULL, 10); - git_config(http_options, NULL); - if (curl_ssl_verify == -1) curl_ssl_verify = 1; @@ -271,6 +339,9 @@ void http_init(struct remote *remote) if (getenv("GIT_CURL_FTP_NO_EPSV")) curl_ftp_no_epsv = 1; + if (remote && remote->url && remote->url[0]) + http_auth_init(remote->url[0]); + #ifndef NO_CURL_EASY_DUPHANDLE curl_default = get_curl_handle(); #endif @@ -305,6 +376,9 @@ void http_cleanup(void) curl_slist_free_all(pragma_header); pragma_header = NULL; + curl_slist_free_all(no_pragma_header); + no_pragma_header = NULL; + if (curl_http_proxy) { free((void *)curl_http_proxy); curl_http_proxy = NULL; @@ -322,15 +396,14 @@ struct active_request_slot *get_active_slot(void) /* Wait for a slot to open up if the queue is full */ while (active_requests >= max_requests) { curl_multi_perform(curlm, &num_transfers); - if (num_transfers < active_requests) { + if (num_transfers < active_requests) process_curl_messages(); - } } #endif - while (slot != NULL && slot->in_use) { + while (slot != NULL && slot->in_use) slot = slot->next; - } + if (slot == NULL) { newslot = xmalloc(sizeof(*newslot)); newslot->curl = NULL; @@ -341,9 +414,8 @@ struct active_request_slot *get_active_slot(void) if (slot == NULL) { active_queue_head = newslot; } else { - while (slot->next != NULL) { + while (slot->next != NULL) slot = slot->next; - } slot->next = newslot; } slot = newslot; @@ -404,7 +476,7 @@ struct fill_chain { struct fill_chain *next; }; -static struct fill_chain *fill_cfg = NULL; +static struct fill_chain *fill_cfg; void add_fill_function(void *data, int (*fill)(void *)) { @@ -535,9 +607,8 @@ static void finish_active_slot(struct active_request_slot *slot) } /* Run callback if appropriate */ - if (slot->callback_func != NULL) { + if (slot->callback_func != NULL) slot->callback_func(slot->callback_data); - } } void finish_all_active_slots(void) @@ -553,6 +624,7 @@ void finish_all_active_slots(void) } } +/* Helpers for modifying and creating URLs */ static inline int needs_quote(int ch) { if (((ch >= 'A') && (ch <= 'Z')) @@ -567,74 +639,605 @@ static inline int needs_quote(int ch) static inline int hex(int v) { - if (v < 10) return '0' + v; - else return 'A' + v - 10; + if (v < 10) + return '0' + v; + else + return 'A' + v - 10; +} + +static void end_url_with_slash(struct strbuf *buf, const char *url) +{ + strbuf_addstr(buf, url); + if (buf->len && buf->buf[buf->len - 1] != '/') + strbuf_addstr(buf, "/"); } static char *quote_ref_url(const char *base, const char *ref) { + struct strbuf buf = STRBUF_INIT; const char *cp; - char *dp, *qref; - int len, baselen, ch; + int ch; - baselen = strlen(base); - len = baselen + 2; /* '/' after base and terminating NUL */ - for (cp = ref; (ch = *cp) != 0; cp++, len++) + end_url_with_slash(&buf, base); + + for (cp = ref; (ch = *cp) != 0; cp++) if (needs_quote(ch)) - len += 2; /* extra two hex plus replacement % */ - qref = xmalloc(len); - memcpy(qref, base, baselen); - dp = qref + baselen; - *(dp++) = '/'; - for (cp = ref; (ch = *cp) != 0; cp++) { - if (needs_quote(ch)) { - *dp++ = '%'; - *dp++ = hex((ch >> 4) & 0xF); - *dp++ = hex(ch & 0xF); - } + strbuf_addf(&buf, "%%%02x", ch); else - *dp++ = ch; - } - *dp = 0; + strbuf_addch(&buf, *cp); - return qref; + return strbuf_detach(&buf, NULL); } -int http_fetch_ref(const char *base, struct ref *ref) +void append_remote_object_url(struct strbuf *buf, const char *url, + const char *hex, + int only_two_digit_prefix) +{ + strbuf_addf(buf, "%s/objects/%.*s/", url, 2, hex); + if (!only_two_digit_prefix) + strbuf_addf(buf, "%s", hex+2); +} + +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); +} + +/* http_request() targets */ +#define HTTP_REQUEST_STRBUF 0 +#define HTTP_REQUEST_FILE 1 + +static int http_request(const char *url, void *result, int target, int options) { - char *url; - struct strbuf buffer = STRBUF_INIT; struct active_request_slot *slot; struct slot_results results; + struct curl_slist *headers = NULL; + struct strbuf buf = STRBUF_INIT; int ret; - url = quote_ref_url(base, ref->name); slot = get_active_slot(); slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + + if (result == NULL) { + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); + } else { + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); + curl_easy_setopt(slot->curl, CURLOPT_FILE, result); + + if (target == HTTP_REQUEST_FILE) { + long posn = ftell(result); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, + fwrite); + if (posn > 0) { + strbuf_addf(&buf, "Range: bytes=%ld-", posn); + headers = curl_slist_append(headers, buf.buf); + strbuf_reset(&buf); + } + slot->local = result; + } else + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, + fwrite_buffer); + } + + strbuf_addstr(&buf, "Pragma:"); + if (options & HTTP_NO_CACHE) + strbuf_addstr(&buf, " no-cache"); + + headers = curl_slist_append(headers, buf.buf); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); + if (start_active_slot(slot)) { run_active_slot(slot); - if (results.curl_result == CURLE_OK) { - strbuf_rtrim(&buffer); - if (buffer.len == 40) - ret = get_sha1_hex(buffer.buf, ref->old_sha1); - else if (!prefixcmp(buffer.buf, "ref: ")) { - ref->symref = xstrdup(buffer.buf + 5); - ret = 0; - } else - ret = 1; - } else { - ret = error("Couldn't get %s for %s\n%s", - url, ref->name, curl_errorstr); - } + if (results.curl_result == CURLE_OK) + ret = HTTP_OK; + else if (missing_target(&results)) + ret = HTTP_MISSING_TARGET; + else + ret = HTTP_ERROR; } else { - ret = error("Unable to start request"); + error("Unable to start HTTP request for %s", url); + ret = HTTP_START_FAILED; + } + + slot->local = NULL; + curl_slist_free_all(headers); + strbuf_release(&buf); + + return ret; +} + +int http_get_strbuf(const char *url, struct strbuf *result, int options) +{ + return http_request(url, result, HTTP_REQUEST_STRBUF, options); +} + +int http_get_file(const char *url, const char *filename, int options) +{ + int ret; + struct strbuf tmpfile = STRBUF_INIT; + FILE *result; + + strbuf_addf(&tmpfile, "%s.temp", filename); + result = fopen(tmpfile.buf, "a"); + if (! result) { + error("Unable to open local file %s", tmpfile.buf); + ret = HTTP_ERROR; + goto cleanup; + } + + ret = http_request(url, result, HTTP_REQUEST_FILE, options); + fclose(result); + + if ((ret == HTTP_OK) && move_temp_to_file(tmpfile.buf, filename)) + ret = HTTP_ERROR; +cleanup: + strbuf_release(&tmpfile); + return ret; +} + +int http_error(const char *url, int ret) +{ + /* http_request has already handled HTTP_START_FAILED. */ + if (ret != HTTP_START_FAILED) + error("%s while accessing %s\n", curl_errorstr, url); + + return ret; +} + +int http_fetch_ref(const char *base, struct ref *ref) +{ + char *url; + struct strbuf buffer = STRBUF_INIT; + int ret = -1; + + url = quote_ref_url(base, ref->name); + if (http_get_strbuf(url, &buffer, HTTP_NO_CACHE) == HTTP_OK) { + strbuf_rtrim(&buffer); + if (buffer.len == 40) + ret = get_sha1_hex(buffer.buf, ref->old_sha1); + else if (!prefixcmp(buffer.buf, "ref: ")) { + ref->symref = xstrdup(buffer.buf + 5); + ret = 0; + } } strbuf_release(&buffer); free(url); return ret; } + +/* Helpers for fetching packs */ +static int fetch_pack_index(unsigned char *sha1, const char *base_url) +{ + int ret = 0; + char *hex = xstrdup(sha1_to_hex(sha1)); + char *filename; + char *url; + struct strbuf buf = STRBUF_INIT; + + /* Don't use the index if the pack isn't there */ + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.pack", hex); + url = strbuf_detach(&buf, 0); + + if (http_get_strbuf(url, NULL, 0)) { + ret = error("Unable to verify pack %s is available", + hex); + goto cleanup; + } + + if (has_pack_index(sha1)) { + ret = 0; + goto cleanup; + } + + if (http_is_verbose) + fprintf(stderr, "Getting index for pack %s\n", hex); + + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.idx", hex); + url = strbuf_detach(&buf, NULL); + + filename = sha1_pack_index_name(sha1); + if (http_get_file(url, filename, 0) != HTTP_OK) + ret = error("Unable to get pack index %s\n", url); + +cleanup: + free(hex); + free(url); + return ret; +} + +static int fetch_and_setup_pack_index(struct packed_git **packs_head, + unsigned char *sha1, const char *base_url) +{ + struct packed_git *new_pack; + + if (fetch_pack_index(sha1, base_url)) + return -1; + + new_pack = parse_pack_index(sha1); + if (!new_pack) + return -1; /* parse_pack_index() already issued error message */ + new_pack->next = *packs_head; + *packs_head = new_pack; + return 0; +} + +int http_get_info_packs(const char *base_url, struct packed_git **packs_head) +{ + int ret = 0, i = 0; + char *url, *data; + struct strbuf buf = STRBUF_INIT; + unsigned char sha1[20]; + + end_url_with_slash(&buf, base_url); + strbuf_addstr(&buf, "objects/info/packs"); + url = strbuf_detach(&buf, NULL); + + ret = http_get_strbuf(url, &buf, HTTP_NO_CACHE); + if (ret != HTTP_OK) + goto cleanup; + + data = buf.buf; + while (i < buf.len) { + switch (data[i]) { + case 'P': + i++; + if (i + 52 <= buf.len && + !prefixcmp(data + i, " pack-") && + !prefixcmp(data + i + 46, ".pack\n")) { + get_sha1_hex(data + i + 6, sha1); + fetch_and_setup_pack_index(packs_head, sha1, + base_url); + i += 51; + break; + } + default: + while (i < buf.len && data[i] != '\n') + i++; + } + i++; + } + +cleanup: + free(url); + return ret; +} + +void release_http_pack_request(struct http_pack_request *preq) +{ + if (preq->packfile != NULL) { + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; + } + if (preq->range_header != NULL) { + curl_slist_free_all(preq->range_header); + preq->range_header = NULL; + } + preq->slot = NULL; + free(preq->url); +} + +int finish_http_pack_request(struct http_pack_request *preq) +{ + int ret; + struct packed_git **lst; + + preq->target->pack_size = ftell(preq->packfile); + + if (preq->packfile != NULL) { + fclose(preq->packfile); + preq->packfile = NULL; + preq->slot->local = NULL; + } + + ret = move_temp_to_file(preq->tmpfile, preq->filename); + if (ret) + return ret; + + lst = preq->lst; + while (*lst != preq->target) + lst = &((*lst)->next); + *lst = (*lst)->next; + + if (verify_pack(preq->target)) + return -1; + install_packed_git(preq->target); + + return 0; +} + +struct http_pack_request *new_http_pack_request( + struct packed_git *target, const char *base_url) +{ + char *url; + char *filename; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct strbuf buf = STRBUF_INIT; + struct http_pack_request *preq; + + preq = xmalloc(sizeof(*preq)); + preq->target = target; + preq->range_header = NULL; + + end_url_with_slash(&buf, base_url); + strbuf_addf(&buf, "objects/pack/pack-%s.pack", + sha1_to_hex(target->sha1)); + url = strbuf_detach(&buf, NULL); + preq->url = xstrdup(url); + + filename = sha1_pack_name(target->sha1); + snprintf(preq->filename, sizeof(preq->filename), "%s", filename); + snprintf(preq->tmpfile, sizeof(preq->tmpfile), "%s.temp", filename); + preq->packfile = fopen(preq->tmpfile, "a"); + if (!preq->packfile) { + error("Unable to open local file %s for pack", + preq->tmpfile); + goto abort; + } + + preq->slot = get_active_slot(); + preq->slot->local = preq->packfile; + curl_easy_setopt(preq->slot->curl, CURLOPT_FILE, preq->packfile); + curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(preq->slot->curl, CURLOPT_URL, url); + curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, + no_pragma_header); + + /* + * If there is data present from a previous transfer attempt, + * resume where it left off + */ + prev_posn = ftell(preq->packfile); + if (prev_posn>0) { + if (http_is_verbose) + fprintf(stderr, + "Resuming fetch of pack %s at byte %ld\n", + sha1_to_hex(target->sha1), prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + preq->range_header = curl_slist_append(NULL, range); + curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, + preq->range_header); + } + + return preq; + +abort: + free(filename); + return NULL; +} + +/* Helpers for fetching objects (loose) */ +static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, + void *data) +{ + unsigned char expn[4096]; + size_t size = eltsize * nmemb; + int posn = 0; + struct http_object_request *freq = + (struct http_object_request *)data; + do { + ssize_t retval = xwrite(freq->localfile, + (char *) ptr + posn, size - posn); + if (retval < 0) + return posn; + posn += retval; + } while (posn < size); + + freq->stream.avail_in = size; + freq->stream.next_in = ptr; + do { + freq->stream.next_out = expn; + freq->stream.avail_out = sizeof(expn); + freq->zret = git_inflate(&freq->stream, Z_SYNC_FLUSH); + git_SHA1_Update(&freq->c, expn, + sizeof(expn) - freq->stream.avail_out); + } while (freq->stream.avail_in && freq->zret == Z_OK); + data_received++; + return size; +} + +struct http_object_request *new_http_object_request(const char *base_url, + unsigned char *sha1) +{ + char *hex = sha1_to_hex(sha1); + char *filename; + char prevfile[PATH_MAX]; + char *url; + int prevlocal; + unsigned char prev_buf[PREV_BUF_SIZE]; + ssize_t prev_read = 0; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + struct http_object_request *freq; + + freq = xmalloc(sizeof(*freq)); + hashcpy(freq->sha1, sha1); + freq->localfile = -1; + + filename = sha1_file_name(sha1); + snprintf(freq->filename, sizeof(freq->filename), "%s", filename); + snprintf(freq->tmpfile, sizeof(freq->tmpfile), + "%s.temp", filename); + + snprintf(prevfile, sizeof(prevfile), "%s.prev", filename); + unlink_or_warn(prevfile); + rename(freq->tmpfile, prevfile); + unlink_or_warn(freq->tmpfile); + + if (freq->localfile != -1) + error("fd leakage in start: %d", freq->localfile); + freq->localfile = open(freq->tmpfile, + O_WRONLY | O_CREAT | O_EXCL, 0666); + /* + * This could have failed due to the "lazy directory creation"; + * try to mkdir the last path component. + */ + if (freq->localfile < 0 && errno == ENOENT) { + char *dir = strrchr(freq->tmpfile, '/'); + if (dir) { + *dir = 0; + mkdir(freq->tmpfile, 0777); + *dir = '/'; + } + freq->localfile = open(freq->tmpfile, + O_WRONLY | O_CREAT | O_EXCL, 0666); + } + + if (freq->localfile < 0) { + error("Couldn't create temporary file %s for %s: %s", + freq->tmpfile, freq->filename, strerror(errno)); + goto abort; + } + + memset(&freq->stream, 0, sizeof(freq->stream)); + + git_inflate_init(&freq->stream); + + git_SHA1_Init(&freq->c); + + url = get_remote_object_url(base_url, hex, 0); + freq->url = xstrdup(url); + + /* + * If a previous temp file is present, process what was already + * fetched. + */ + prevlocal = open(prevfile, O_RDONLY); + if (prevlocal != -1) { + do { + prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE); + if (prev_read>0) { + if (fwrite_sha1_file(prev_buf, + 1, + prev_read, + freq) == prev_read) { + prev_posn += prev_read; + } else { + prev_read = -1; + } + } + } while (prev_read > 0); + close(prevlocal); + } + unlink_or_warn(prevfile); + + /* + * Reset inflate/SHA1 if there was an error reading the previous temp + * file; also rewind to the beginning of the local file. + */ + if (prev_read == -1) { + memset(&freq->stream, 0, sizeof(freq->stream)); + git_inflate_init(&freq->stream); + git_SHA1_Init(&freq->c); + if (prev_posn>0) { + prev_posn = 0; + lseek(freq->localfile, 0, SEEK_SET); + ftruncate(freq->localfile, 0); + } + } + + freq->slot = get_active_slot(); + + curl_easy_setopt(freq->slot->curl, CURLOPT_FILE, freq); + curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); + curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr); + curl_easy_setopt(freq->slot->curl, CURLOPT_URL, url); + curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + + /* + * If we have successfully processed data from a previous fetch + * attempt, only fetch the data we don't already have. + */ + if (prev_posn>0) { + if (http_is_verbose) + fprintf(stderr, + "Resuming fetch of object %s at byte %ld\n", + hex, prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(freq->slot->curl, + CURLOPT_HTTPHEADER, range_header); + } + + return freq; + + free(url); +abort: + free(filename); + free(freq); + return NULL; +} + +void process_http_object_request(struct http_object_request *freq) +{ + if (freq->slot == NULL) + return; + freq->curl_result = freq->slot->curl_result; + freq->http_code = freq->slot->http_code; + freq->slot = NULL; +} + +int finish_http_object_request(struct http_object_request *freq) +{ + struct stat st; + + close(freq->localfile); + freq->localfile = -1; + + process_http_object_request(freq); + + if (freq->http_code == 416) { + fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); + } else if (freq->curl_result != CURLE_OK) { + if (stat(freq->tmpfile, &st) == 0) + if (st.st_size == 0) + unlink_or_warn(freq->tmpfile); + return -1; + } + + git_inflate_end(&freq->stream); + git_SHA1_Final(freq->real_sha1, &freq->c); + if (freq->zret != Z_STREAM_END) { + unlink_or_warn(freq->tmpfile); + return -1; + } + if (hashcmp(freq->sha1, freq->real_sha1)) { + unlink_or_warn(freq->tmpfile); + return -1; + } + freq->rename = + move_temp_to_file(freq->tmpfile, freq->filename); + + return freq->rename; +} + +void abort_http_object_request(struct http_object_request *freq) +{ + unlink_or_warn(freq->tmpfile); + + release_http_object_request(freq); +} + +void release_http_object_request(struct http_object_request *freq) +{ + if (freq->localfile != -1) { + close(freq->localfile); + freq->localfile = -1; + } + if (freq->url != NULL) { + free(freq->url); + freq->url = NULL; + } + freq->slot = NULL; +} @@ -37,6 +37,10 @@ #define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND #endif +#if LIBCURL_VERSION_NUM < 0x070c03 +#define NO_CURL_IOCTL +#endif + struct slot_results { CURLcode curl_result; @@ -67,6 +71,9 @@ struct buffer extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf); extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf); extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf); +#ifndef NO_CURL_IOCTL +extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp); +#endif /* Slot lifecycle functions */ extern struct active_request_slot *get_active_slot(void); @@ -86,6 +93,7 @@ extern void http_cleanup(void); extern int data_received; extern int active_requests; +extern int http_is_verbose; extern char curl_errorstr[CURL_ERROR_SIZE]; @@ -102,6 +110,90 @@ static inline int missing__target(int code, int result) #define missing_target(a) missing__target((a)->http_code, (a)->curl_result) +/* Helpers for modifying and creating URLs */ +extern void append_remote_object_url(struct strbuf *buf, const char *url, + const char *hex, + int only_two_digit_prefix); +extern char *get_remote_object_url(const char *url, const char *hex, + int only_two_digit_prefix); + +/* Options for http_request_*() */ +#define HTTP_NO_CACHE 1 + +/* Return values for http_request_*() */ +#define HTTP_OK 0 +#define HTTP_MISSING_TARGET 1 +#define HTTP_ERROR 2 +#define HTTP_START_FAILED 3 + +/* + * Requests an url and stores the result in a strbuf. + * + * If the result pointer is NULL, a HTTP HEAD request is made instead of GET. + */ +int http_get_strbuf(const char *url, struct strbuf *result, int options); + +/* + * Downloads an url and stores the result in the given file. + * + * If a previous interrupted download is detected (i.e. a previous temporary + * file is still around) the download is resumed. + */ +int http_get_file(const char *url, const char *filename, int options); + +/* + * Prints an error message using error() containing url and curl_errorstr, + * and returns ret. + */ +int http_error(const char *url, int ret); + extern int http_fetch_ref(const char *base, struct ref *ref); +/* Helpers for fetching packs */ +extern int http_get_info_packs(const char *base_url, + struct packed_git **packs_head); + +struct http_pack_request +{ + char *url; + struct packed_git *target; + struct packed_git **lst; + FILE *packfile; + char filename[PATH_MAX]; + char tmpfile[PATH_MAX]; + struct curl_slist *range_header; + struct active_request_slot *slot; +}; + +extern struct http_pack_request *new_http_pack_request( + struct packed_git *target, const char *base_url); +extern int finish_http_pack_request(struct http_pack_request *preq); +extern void release_http_pack_request(struct http_pack_request *preq); + +/* Helpers for fetching object */ +struct http_object_request +{ + char *url; + char filename[PATH_MAX]; + char tmpfile[PATH_MAX]; + int localfile; + CURLcode curl_result; + char errorstr[CURL_ERROR_SIZE]; + long http_code; + unsigned char sha1[20]; + unsigned char real_sha1[20]; + git_SHA_CTX c; + z_stream stream; + int zret; + int rename; + struct active_request_slot *slot; +}; + +extern struct http_object_request *new_http_object_request( + const char *base_url, unsigned char *sha1); +extern void process_http_object_request(struct http_object_request *freq); +extern int finish_http_object_request(struct http_object_request *freq); +extern void abort_http_object_request(struct http_object_request *freq); +extern void release_http_object_request(struct http_object_request *freq); + #endif /* HTTP_H */ diff --git a/imap-send.c b/imap-send.c index f91293c23f..e4c83b9d5b 100644 --- a/imap-send.c +++ b/imap-send.c @@ -135,6 +135,7 @@ struct imap_server_conf { char *pass; int use_ssl; int ssl_verify; + int use_html; }; struct imap_store_conf { @@ -578,7 +579,7 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx, n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen); free(cmd->cb.data); if (n != cmd->cb.dlen || - (n = socket_write(&imap->buf.sock, "\r\n", 2)) != 2) { + socket_write(&imap->buf.sock, "\r\n", 2) != 2) { free(cmd->cmd); free(cmd); return NULL; @@ -981,9 +982,7 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) struct imap_store *ctx; struct imap *imap; char *arg, *rsp; - struct hostent *he; - struct sockaddr_in addr; - int s, a[2], preauth; + int s = -1, a[2], preauth; pid_t pid; ctx = xcalloc(sizeof(*ctx), 1); @@ -1020,6 +1019,51 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) imap_info("ok\n"); } else { +#ifndef NO_IPV6 + struct addrinfo hints, *ai0, *ai; + int gai; + char portstr[6]; + + snprintf(portstr, sizeof(portstr), "%hu", srvc->port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + imap_info("Resolving %s... ", srvc->host); + gai = getaddrinfo(srvc->host, portstr, &hints, &ai); + if (gai) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai)); + goto bail; + } + imap_info("ok\n"); + + for (ai0 = ai; ai; ai = ai->ai_next) { + char addr[NI_MAXHOST]; + + s = socket(ai->ai_family, ai->ai_socktype, + ai->ai_protocol); + if (s < 0) + continue; + + getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, + sizeof(addr), NULL, 0, NI_NUMERICHOST); + imap_info("Connecting to [%s]:%s... ", addr, portstr); + + if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) { + close(s); + s = -1; + perror("connect"); + continue; + } + + break; + } + freeaddrinfo(ai0); +#else /* NO_IPV6 */ + struct hostent *he; + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); addr.sin_port = htons(srvc->port); addr.sin_family = AF_INET; @@ -1039,7 +1083,12 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) imap_info("Connecting to %s:%hu... ", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); if (connect(s, (struct sockaddr *)&addr, sizeof(addr))) { close(s); + s = -1; perror("connect"); + } +#endif + if (s < 0) { + fputs("Error: unable to connect to server.\n", stderr); goto bail; } @@ -1263,6 +1312,53 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid) return DRV_OK; } +static void encode_html_chars(struct strbuf *p) +{ + int i; + for (i = 0; i < p->len; i++) { + if (p->buf[i] == '&') + strbuf_splice(p, i, 1, "&", 5); + if (p->buf[i] == '<') + strbuf_splice(p, i, 1, "<", 4); + if (p->buf[i] == '>') + strbuf_splice(p, i, 1, ">", 4); + if (p->buf[i] == '"') + strbuf_splice(p, i, 1, """, 6); + } +} +static void wrap_in_html(struct msg_data *msg) +{ + struct strbuf buf = STRBUF_INIT; + struct strbuf **lines; + struct strbuf **p; + static char *content_type = "Content-Type: text/html;\n"; + static char *pre_open = "<pre>\n"; + static char *pre_close = "</pre>\n"; + int added_header = 0; + + strbuf_attach(&buf, msg->data, msg->len, msg->len); + lines = strbuf_split(&buf, '\n'); + strbuf_release(&buf); + for (p = lines; *p; p++) { + if (! added_header) { + if ((*p)->len == 1 && *((*p)->buf) == '\n') { + strbuf_addstr(&buf, content_type); + strbuf_addbuf(&buf, *p); + strbuf_addstr(&buf, pre_open); + added_header = 1; + continue; + } + } + else + encode_html_chars(*p); + strbuf_addbuf(&buf, *p); + } + strbuf_addstr(&buf, pre_close); + strbuf_list_free(lines); + msg->len = buf.len; + msg->data = strbuf_detach(&buf, NULL); +} + #define CHUNKSIZE 0x1000 static int read_message(FILE *f, struct msg_data *msg) @@ -1339,6 +1435,7 @@ static struct imap_server_conf server = { NULL, /* pass */ 0, /* use_ssl */ 1, /* ssl_verify */ + 0, /* use_html */ }; static char *imap_folder; @@ -1377,6 +1474,8 @@ static int git_imap_config(const char *key, const char *val, void *cb) server.tunnel = xstrdup(val); else if (!strcmp("sslverify", key)) server.ssl_verify = git_config_bool(key, val); + else if (!strcmp("preformattedHTML", key)) + server.use_html = git_config_bool(key, val); return 0; } @@ -1439,6 +1538,8 @@ int main(int argc, char **argv) fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total); if (!split_msg(&all_msgs, &msg, &ofs)) break; + if (server.use_html) + wrap_in_html(&msg); r = imap_store_msg(ctx, &msg, &uid); if (r != DRV_OK) break; diff --git a/index-pack.c b/index-pack.c index 5dfe03ee6c..0c92bafcbb 100644 --- a/index-pack.c +++ b/index-pack.c @@ -232,7 +232,7 @@ static void free_base_data(struct base_data *c) static void prune_base_data(struct base_data *retain) { - struct base_data *b = base_cache; + struct base_data *b; for (b = base_cache; base_cache_used > delta_base_cache_limit && b; b = b->child) { @@ -293,8 +293,8 @@ static void *unpack_entry_data(unsigned long offset, unsigned long size) static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_base) { - unsigned char *p, c; - unsigned long size; + unsigned char *p; + unsigned long size, c; off_t base_offset; unsigned shift; void *data; @@ -312,7 +312,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ p = fill(1); c = *p; use(1); - size += (c & 0x7fUL) << shift; + size += (c & 0x7f) << shift; shift += 7; } obj->size = size; diff --git a/levenshtein.c b/levenshtein.c index a32f4cdc45..fc281597fd 100644 --- a/levenshtein.c +++ b/levenshtein.c @@ -27,7 +27,7 @@ * * It does so by calculating the costs of the path ending in characters * i (in string1) and j (in string2), respectively, given that the last - * operation is a substition, a swap, a deletion, or an insertion. + * operation is a substitution, a swap, a deletion, or an insertion. * * This implementation allows the costs to be weighted: * diff --git a/list-objects.c b/list-objects.c index 30ded3d4dd..8953548c07 100644 --- a/list-objects.c +++ b/list-objects.c @@ -141,14 +141,15 @@ static void add_pending_tree(struct rev_info *revs, struct tree *tree) void traverse_commit_list(struct rev_info *revs, show_commit_fn show_commit, - show_object_fn show_object) + show_object_fn show_object, + void *data) { int i; struct commit *commit; while ((commit = get_revision(revs)) != NULL) { add_pending_tree(revs, commit->tree); - show_commit(commit); + show_commit(commit, data); } for (i = 0; i < revs->pending.nr; i++) { struct object_array_entry *pending = revs->pending.objects + i; diff --git a/list-objects.h b/list-objects.h index 0b2de64301..d65dbf03e6 100644 --- a/list-objects.h +++ b/list-objects.h @@ -1,11 +1,11 @@ #ifndef LIST_OBJECTS_H #define LIST_OBJECTS_H -typedef void (*show_commit_fn)(struct commit *); +typedef void (*show_commit_fn)(struct commit *, void *); 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); +void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *); void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn); diff --git a/ll-merge.c b/ll-merge.c index fa2ca5250c..9168958e86 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -175,8 +175,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, { "B", temp[2] }, { NULL } }; - struct child_process child; - const char *args[20]; + const char *args[] = { "sh", "-c", NULL, NULL }; int status, fd, i; struct stat st; @@ -191,14 +190,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, strbuf_expand(&cmd, fn->cmdline, strbuf_expand_dict_cb, &dict); - memset(&child, 0, sizeof(child)); - child.argv = args; - args[0] = "sh"; - args[1] = "-c"; args[2] = cmd.buf; - args[3] = NULL; - - status = run_command(&child); + status = run_command_v_opt(args, 0); if (status < -ERR_RUN_COMMAND_FORK) ; /* failure in run-command */ else @@ -219,7 +212,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, close(fd); bad: for (i = 0; i < 3; i++) - unlink(temp[i]); + unlink_or_warn(temp[i]); strbuf_release(&cmd); return status; } @@ -238,7 +231,7 @@ static int read_merge_config(const char *var, const char *value, void *cb) if (!strcmp(var, "merge.default")) { if (value) - default_ll_merge = strdup(value); + default_ll_merge = xstrdup(value); return 0; } @@ -272,7 +265,7 @@ static int read_merge_config(const char *var, const char *value, void *cb) if (!strcmp("name", ep)) { if (!value) return error("%s: lacks value", var); - fn->description = strdup(value); + fn->description = xstrdup(value); return 0; } @@ -295,14 +288,14 @@ static int read_merge_config(const char *var, const char *value, void *cb) * file named by %A, and signal that it has done with zero exit * status. */ - fn->cmdline = strdup(value); + fn->cmdline = xstrdup(value); return 0; } if (!strcmp("recursive", ep)) { if (!value) return error("%s: lacks value", var); - fn->recursive = strdup(value); + fn->recursive = xstrdup(value); return 0; } diff --git a/lockfile.c b/lockfile.c index 3dbb2d1ff9..eb931eded5 100644 --- a/lockfile.c +++ b/lockfile.c @@ -16,7 +16,7 @@ static void remove_lock_file(void) lock_file_list->filename[0]) { if (lock_file_list->fd >= 0) close(lock_file_list->fd); - unlink(lock_file_list->filename); + unlink_or_warn(lock_file_list->filename); } lock_file_list = lock_file_list->next; } @@ -109,7 +109,7 @@ static char *resolve_symlink(char *p, size_t s) * link is a relative path, so I must replace the * last element of p with it. */ - char *r = (char*)last_path_elm(p); + char *r = (char *)last_path_elm(p); if (r - p + link_len < s) strcpy(r, link); else { @@ -259,7 +259,7 @@ void rollback_lock_file(struct lock_file *lk) if (lk->filename[0]) { if (lk->fd >= 0) close(lk->fd); - unlink(lk->filename); + unlink_or_warn(lk->filename); } lk->filename[0] = 0; } diff --git a/log-tree.c b/log-tree.c index 84a74e544b..59d63eb67e 100644 --- a/log-tree.c +++ b/log-tree.c @@ -6,6 +6,7 @@ #include "log-tree.h" #include "reflog-walk.h" #include "refs.h" +#include "string-list.h" struct decoration name_decoration = { "object names" }; @@ -24,6 +25,7 @@ static int add_ref_decoration(const char *refname, const unsigned char *sha1, in struct object *obj = parse_object(sha1); if (!obj) return 0; + refname = prettify_refname(refname); add_name_decoration("", refname, obj); while (obj->type == OBJ_TAG) { obj = ((struct tag *)obj)->tagged; @@ -79,18 +81,18 @@ void show_decorations(struct rev_info *opt, struct commit *commit) */ static int detect_any_signoff(char *letter, int size) { - char ch, *cp; + char *cp; int seen_colon = 0; int seen_at = 0; int seen_name = 0; int seen_head = 0; cp = letter + size; - while (letter <= --cp && (ch = *cp) == '\n') + while (letter <= --cp && *cp == '\n') continue; while (letter <= cp) { - ch = *cp--; + char ch = *cp--; if (ch == '\n') break; @@ -178,13 +180,31 @@ static int has_non_ascii(const char *s) return 0; } -void log_write_email_headers(struct rev_info *opt, const char *name, +void get_patch_filename(struct commit *commit, int nr, const char *suffix, + struct strbuf *buf) +{ + int suffix_len = strlen(suffix) + 1; + int start_len = buf->len; + + strbuf_addf(buf, commit ? "%04d-" : "%d", nr); + if (commit) { + int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; + + format_commit_message(commit, "%f", buf, DATE_NORMAL); + if (max_len < buf->len) + strbuf_setlen(buf, max_len); + strbuf_addstr(buf, suffix); + } +} + +void log_write_email_headers(struct rev_info *opt, struct commit *commit, const char **subject_p, const char **extra_headers_p, int *need_8bit_cte_p) { const char *subject = NULL; const char *extra_headers = opt->extra_headers; + const char *name = sha1_to_hex(commit->object.sha1); *need_8bit_cte_p = 0; /* unknown */ if (opt->total > 0) { @@ -211,14 +231,19 @@ void log_write_email_headers(struct rev_info *opt, const char *name, printf("Message-Id: <%s>\n", opt->message_id); graph_show_oneline(opt->graph); } - if (opt->ref_message_id) { - printf("In-Reply-To: <%s>\nReferences: <%s>\n", - opt->ref_message_id, opt->ref_message_id); + if (opt->ref_message_ids && opt->ref_message_ids->nr > 0) { + int i, n; + n = opt->ref_message_ids->nr; + printf("In-Reply-To: <%s>\n", opt->ref_message_ids->items[n-1].string); + for (i = 0; i < n; i++) + printf("%s<%s>\n", (i > 0 ? "\t" : "References: "), + opt->ref_message_ids->items[i].string); graph_show_oneline(opt->graph); } if (opt->mime_boundary) { static char subject_buffer[1024]; static char buffer[1024]; + struct strbuf filename = STRBUF_INIT; *need_8bit_cte_p = -1; /* NEVER */ snprintf(subject_buffer, sizeof(subject_buffer) - 1, "%s" @@ -237,18 +262,21 @@ void log_write_email_headers(struct rev_info *opt, const char *name, mime_boundary_leader, opt->mime_boundary); extra_headers = subject_buffer; + get_patch_filename(opt->numbered_files ? NULL : commit, opt->nr, + opt->patch_suffix, &filename); snprintf(buffer, sizeof(buffer) - 1, "\n--%s%s\n" "Content-Type: text/x-patch;" - " name=\"%s.diff\"\n" + " name=\"%s\"\n" "Content-Transfer-Encoding: 8bit\n" "Content-Disposition: %s;" - " filename=\"%s.diff\"\n\n", + " filename=\"%s\"\n\n", mime_boundary_leader, opt->mime_boundary, - name, + filename.buf, opt->no_inline ? "attachment" : "inline", - name); + filename.buf); opt->diffopt.stat_sep = buffer; + strbuf_release(&filename); } *subject_p = subject; *extra_headers_p = extra_headers; @@ -328,8 +356,7 @@ void show_log(struct rev_info *opt) */ if (opt->commit_format == CMIT_FMT_EMAIL) { - log_write_email_headers(opt, sha1_to_hex(commit->object.sha1), - &subject, &extra_headers, + log_write_email_headers(opt, commit, &subject, &extra_headers, &need_8bit_cte); } else if (opt->commit_format != CMIT_FMT_USERFORMAT) { fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout); diff --git a/log-tree.h b/log-tree.h index f2a90084ae..20b5caf1aa 100644 --- a/log-tree.h +++ b/log-tree.h @@ -13,10 +13,14 @@ int log_tree_commit(struct rev_info *, struct commit *); int log_tree_opt_parse(struct rev_info *, const char **, int); void show_log(struct rev_info *opt); void show_decorations(struct rev_info *opt, struct commit *commit); -void log_write_email_headers(struct rev_info *opt, const char *name, +void log_write_email_headers(struct rev_info *opt, struct commit *commit, const char **subject_p, const char **extra_headers_p, int *need_8bit_cte_p); void load_ref_decorations(void); +#define FORMAT_PATCH_NAME_MAX 64 +void get_patch_filename(struct commit *commit, int nr, const char *suffix, + struct strbuf *buf); + #endif @@ -99,7 +99,8 @@ static void add_mapping(struct string_list *map, old_name, old_email, new_name, new_email); } -static char *parse_name_and_email(char *buffer, char **name, char **email) +static char *parse_name_and_email(char *buffer, char **name, + char **email, int allow_empty_email) { char *left, *right, *nstart, *nend; *name = *email = 0; @@ -108,7 +109,7 @@ static char *parse_name_and_email(char *buffer, char **name, char **email) return NULL; if ((right = strchr(left+1, '>')) == NULL) return NULL; - if (left+1 == right) + if (!allow_empty_email && (left+1 == right)) return NULL; /* remove whitespace from beginning and end of name */ @@ -159,8 +160,8 @@ static int read_single_mailmap(struct string_list *map, const char *filename, ch } continue; } - if ((name2 = parse_name_and_email(buffer, &name1, &email1)) != NULL) - parse_name_and_email(name2, &name2, &email2); + if ((name2 = parse_name_and_email(buffer, &name1, &email1, 0)) != NULL) + parse_name_and_email(name2, &name2, &email2, 1); if (email1) add_mapping(map, name1, email1, name2, email2); diff --git a/merge-index.c b/merge-index.c index aa9cf23a39..19ddd03e0b 100644 --- a/merge-index.c +++ b/merge-index.c @@ -3,45 +3,20 @@ #include "exec_cmd.h" static const char *pgm; -static const char *arguments[9]; static int one_shot, quiet; static int err; -static void run_program(void) -{ - struct child_process child; - memset(&child, 0, sizeof(child)); - child.argv = arguments; - if (run_command(&child)) { - if (one_shot) { - err++; - } else { - if (!quiet) - die("merge program failed"); - exit(1); - } - } -} - static int merge_entry(int pos, const char *path) { int found; + const char *arguments[] = { pgm, "", "", "", path, "", "", "", NULL }; + char hexbuf[4][60]; + char ownbuf[4][60]; if (pos >= active_nr) die("git merge-index: %s not in the cache", path); - arguments[0] = pgm; - arguments[1] = ""; - arguments[2] = ""; - arguments[3] = ""; - arguments[4] = path; - arguments[5] = ""; - arguments[6] = ""; - arguments[7] = ""; - arguments[8] = NULL; found = 0; do { - static char hexbuf[4][60]; - static char ownbuf[4][60]; struct cache_entry *ce = active_cache[pos]; int stage = ce_stage(ce); @@ -55,7 +30,16 @@ static int merge_entry(int pos, const char *path) } while (++pos < active_nr); if (!found) die("git merge-index: %s not in the cache", path); - run_program(); + + if (run_command_v_opt(arguments, 0)) { + if (one_shot) + err++; + else { + if (!quiet) + die("merge program failed"); + exit(1); + } + } return found; } diff --git a/merge-recursive.c b/merge-recursive.c index 9bf5cc7175..f5df9b961b 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -520,8 +520,12 @@ static void update_file_flags(struct merge_options *o, unsigned long size; if (S_ISGITLINK(mode)) - die("cannot read object %s '%s': It is a submodule!", - sha1_to_hex(sha), path); + /* + * We may later decide to recursively descend into + * the submodule directory and update its index + * and/or work tree, but we do not do that now. + */ + goto update_index; buf = read_sha1_file(sha, &type, &size); if (!buf) @@ -801,22 +805,19 @@ static int process_renames(struct merge_options *o, } for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) { - int compare; char *src; - struct string_list *renames1, *renames2, *renames2Dst; + struct string_list *renames1, *renames2Dst; struct rename *ren1 = NULL, *ren2 = NULL; const char *branch1, *branch2; const char *ren1_src, *ren1_dst; if (i >= a_renames->nr) { - compare = 1; ren2 = b_renames->items[j++].util; } else if (j >= b_renames->nr) { - compare = -1; ren1 = a_renames->items[i++].util; } else { - compare = strcmp(a_renames->items[i].string, - b_renames->items[j].string); + int compare = strcmp(a_renames->items[i].string, + b_renames->items[j].string); if (compare <= 0) ren1 = a_renames->items[i++].util; if (compare >= 0) @@ -826,14 +827,12 @@ static int process_renames(struct merge_options *o, /* TODO: refactor, so that 1/2 are not needed */ if (ren1) { renames1 = a_renames; - renames2 = b_renames; renames2Dst = &b_by_dst; branch1 = o->branch1; branch2 = o->branch2; } else { struct rename *tmp; renames1 = b_renames; - renames2 = a_renames; renames2Dst = &a_by_dst; branch1 = o->branch2; branch2 = o->branch1; @@ -934,11 +933,12 @@ static int process_renames(struct merge_options *o, ren1_src, ren1_dst, branch1, branch2); update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst); - update_stages(ren1_dst, NULL, - branch1 == o->branch1 ? - ren1->pair->two : NULL, - branch1 == o->branch1 ? - NULL : ren1->pair->two, 1); + if (!o->call_depth) + update_stages(ren1_dst, NULL, + branch1 == o->branch1 ? + ren1->pair->two : NULL, + branch1 == o->branch1 ? + NULL : ren1->pair->two, 1); } else if (!sha_eq(dst_other.sha1, null_sha1)) { const char *new_path; clean_merge = 0; diff --git a/mktree.c b/mktree.c deleted file mode 100644 index 137a0950f6..0000000000 --- a/mktree.c +++ /dev/null @@ -1,131 +0,0 @@ -/* - * GIT - the stupid content tracker - * - * Copyright (c) Junio C Hamano, 2006 - */ -#include "cache.h" -#include "quote.h" -#include "tree.h" -#include "exec_cmd.h" - -static struct treeent { - unsigned mode; - unsigned char sha1[20]; - int len; - char name[FLEX_ARRAY]; -} **entries; -static int alloc, used; - -static void append_to_tree(unsigned mode, unsigned char *sha1, char *path) -{ - struct treeent *ent; - int len = strlen(path); - if (strchr(path, '/')) - die("path %s contains slash", path); - - if (alloc <= used) { - alloc = alloc_nr(used); - entries = xrealloc(entries, sizeof(*entries) * alloc); - } - ent = entries[used++] = xmalloc(sizeof(**entries) + len + 1); - ent->mode = mode; - ent->len = len; - hashcpy(ent->sha1, sha1); - memcpy(ent->name, path, len+1); -} - -static int ent_compare(const void *a_, const void *b_) -{ - struct treeent *a = *(struct treeent **)a_; - struct treeent *b = *(struct treeent **)b_; - return base_name_compare(a->name, a->len, a->mode, - b->name, b->len, b->mode); -} - -static void write_tree(unsigned char *sha1) -{ - struct strbuf buf; - size_t size; - int i; - - qsort(entries, used, sizeof(*entries), ent_compare); - for (size = i = 0; i < used; i++) - size += 32 + entries[i]->len; - - strbuf_init(&buf, size); - for (i = 0; i < used; i++) { - struct treeent *ent = entries[i]; - strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0'); - strbuf_add(&buf, ent->sha1, 20); - } - - write_sha1_file(buf.buf, buf.len, tree_type, sha1); -} - -static const char mktree_usage[] = "git mktree [-z]"; - -int main(int ac, char **av) -{ - struct strbuf sb = STRBUF_INIT; - struct strbuf p_uq = STRBUF_INIT; - unsigned char sha1[20]; - int line_termination = '\n'; - - git_extract_argv0_path(av[0]); - - setup_git_directory(); - - while ((1 < ac) && av[1][0] == '-') { - char *arg = av[1]; - if (!strcmp("-z", arg)) - line_termination = 0; - else - usage(mktree_usage); - ac--; - av++; - } - - while (strbuf_getline(&sb, stdin, line_termination) != EOF) { - char *ptr, *ntr; - unsigned mode; - enum object_type type; - char *path; - - ptr = sb.buf; - /* Input is non-recursive ls-tree output format - * mode SP type SP sha1 TAB name - */ - mode = strtoul(ptr, &ntr, 8); - if (ptr == ntr || !ntr || *ntr != ' ') - die("input format error: %s", sb.buf); - ptr = ntr + 1; /* type */ - ntr = strchr(ptr, ' '); - if (!ntr || sb.buf + sb.len <= ntr + 40 || - ntr[41] != '\t' || - get_sha1_hex(ntr + 1, sha1)) - die("input format error: %s", sb.buf); - type = sha1_object_info(sha1, NULL); - if (type < 0) - die("object %s unavailable", sha1_to_hex(sha1)); - *ntr++ = 0; /* now at the beginning of SHA1 */ - if (type != type_from_string(ptr)) - die("object type %s mismatch (%s)", ptr, typename(type)); - - path = ntr + 41; /* at the beginning of name */ - if (line_termination && path[0] == '"') { - strbuf_reset(&p_uq); - if (unquote_c_style(&p_uq, path, NULL)) { - die("invalid quoting"); - } - path = p_uq.buf; - } - - append_to_tree(mode, sha1, path); - } - strbuf_release(&p_uq); - strbuf_release(&sb); - - write_tree(sha1); - puts(sha1_to_hex(sha1)); - exit(0); -} @@ -45,13 +45,14 @@ int type_from_string(const char *str) static unsigned int hash_obj(struct object *obj, unsigned int n) { - unsigned int hash = *(unsigned int *)obj->sha1; + unsigned int hash; + memcpy(&hash, obj->sha1, sizeof(unsigned int)); return hash % n; } static void insert_obj_hash(struct object *obj, struct object **hash, unsigned int size) { - int j = hash_obj(obj, size); + unsigned int j = hash_obj(obj, size); while (hash[j]) { j++; @@ -61,16 +62,16 @@ static void insert_obj_hash(struct object *obj, struct object **hash, unsigned i hash[j] = obj; } -static int hashtable_index(const unsigned char *sha1) +static unsigned int hashtable_index(const unsigned char *sha1) { unsigned int i; memcpy(&i, sha1, sizeof(unsigned int)); - return (int)(i % obj_hash_size); + return i % obj_hash_size; } struct object *lookup_object(const unsigned char *sha1) { - int i; + unsigned int i; struct object *obj; if (!obj_hash) diff --git a/pack-check.c b/pack-check.c index 90c33b1b84..166ca703c1 100644 --- a/pack-check.c +++ b/pack-check.c @@ -49,7 +49,7 @@ static int verify_packfile(struct packed_git *p, const unsigned char *index_base = p->index_data; git_SHA_CTX ctx; unsigned char sha1[20], *pack_sig; - off_t offset = 0, pack_sig_ofs = p->pack_size - 20; + off_t offset = 0, pack_sig_ofs = 0; uint32_t nr_objects, i; int err = 0; struct idx_entry *entries; @@ -61,14 +61,16 @@ static int verify_packfile(struct packed_git *p, */ git_SHA1_Init(&ctx); - while (offset < pack_sig_ofs) { + do { unsigned int remaining; unsigned char *in = use_pack(p, w_curs, offset, &remaining); offset += remaining; + if (!pack_sig_ofs) + pack_sig_ofs = p->pack_size - 20; if (offset > pack_sig_ofs) remaining -= (unsigned int)(offset - pack_sig_ofs); git_SHA1_Update(&ctx, in, remaining); - } + } while (offset < pack_sig_ofs); git_SHA1_Final(sha1, &ctx); pack_sig = use_pack(p, w_curs, pack_sig_ofs, NULL); if (hashcmp(sha1, pack_sig)) diff --git a/pack-refs.c b/pack-refs.c index 2c76fb181f..301fc60eae 100644 --- a/pack-refs.c +++ b/pack-refs.c @@ -66,7 +66,7 @@ static void prune_ref(struct ref_to_prune *r) struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1); if (lock) { - unlink(git_path("%s", r->name)); + unlink_or_warn(git_path("%s", r->name)); unlock_ref(lock); } } diff --git a/parse-options.c b/parse-options.c index 4c5d09dd25..34282adde5 100644 --- a/parse-options.c +++ b/parse-options.c @@ -31,11 +31,20 @@ static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt, return 0; } +static void fix_filename(const char *prefix, const char **file) +{ + if (!file || !*file || !prefix || is_absolute_path(*file) + || !strcmp("-", *file)) + return; + *file = xstrdup(prefix_filename(prefix, strlen(prefix), *file)); +} + static int get_value(struct parse_opt_ctx_t *p, const struct option *opt, int flags) { const char *s, *arg; const int unset = flags & OPT_UNSET; + int err; if (unset && p->opt) return opterror(opt, "takes no value", flags); @@ -50,6 +59,7 @@ static int get_value(struct parse_opt_ctx_t *p, /* FALLTHROUGH */ case OPTION_BOOLEAN: case OPTION_BIT: + case OPTION_NEGBIT: case OPTION_SET_INT: case OPTION_SET_PTR: return opterror(opt, "takes no value", flags); @@ -66,6 +76,13 @@ static int get_value(struct parse_opt_ctx_t *p, *(int *)opt->value |= opt->defval; return 0; + case OPTION_NEGBIT: + if (unset) + *(int *)opt->value |= opt->defval; + else + *(int *)opt->value &= ~opt->defval; + return 0; + case OPTION_BOOLEAN: *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1; return 0; @@ -87,6 +104,19 @@ static int get_value(struct parse_opt_ctx_t *p, return get_arg(p, opt, flags, (const char **)opt->value); return 0; + case OPTION_FILENAME: + err = 0; + if (unset) + *(const char **)opt->value = NULL; + else if (opt->flags & PARSE_OPT_OPTARG && !p->opt) + *(const char **)opt->value = (const char *)opt->defval; + else + err = get_arg(p, opt, flags, (const char **)opt->value); + + if (!err) + fix_filename(p->prefix, (const char **)opt->value); + return err; + case OPTION_CALLBACK: if (unset) return (*opt->callback)(opt, NULL, 1) ? (-1) : 0; @@ -121,11 +151,33 @@ static int get_value(struct parse_opt_ctx_t *p, static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options) { + const struct option *numopt = NULL; + for (; options->type != OPTION_END; options++) { if (options->short_name == *p->opt) { p->opt = p->opt[1] ? p->opt + 1 : NULL; return get_value(p, options, OPT_SHORT); } + + /* + * Handle the numerical option later, explicit one-digit + * options take precedence over it. + */ + if (options->type == OPTION_NUMBER) + numopt = options; + } + if (numopt && isdigit(*p->opt)) { + size_t len = 1; + char *arg; + int rc; + + while (isdigit(p->opt[len])) + len++; + arg = xmemdupz(p->opt, len); + p->opt = p->opt[len] ? p->opt + len : NULL; + rc = (*numopt->callback)(numopt, arg, 0) ? (-1) : 0; + free(arg); + return rc; } return -2; } @@ -215,6 +267,25 @@ is_abbreviated: return -2; } +static int parse_nodash_opt(struct parse_opt_ctx_t *p, const char *arg, + const struct option *options) +{ + for (; options->type != OPTION_END; options++) { + if (!(options->flags & PARSE_OPT_NODASH)) + continue; + if ((options->flags & PARSE_OPT_OPTARG) || + !(options->flags & PARSE_OPT_NOARG)) + die("BUG: dashless options don't support arguments"); + if (!(options->flags & PARSE_OPT_NONEG)) + die("BUG: dashless options don't support negation"); + if (options->long_name) + die("BUG: dashless options can't be long"); + if (options->short_name == arg[0] && arg[1] == '\0') + return get_value(p, options, OPT_SHORT); + } + return -2; +} + static void check_typos(const char *arg, const struct option *options) { if (strlen(arg) < 3) @@ -235,15 +306,42 @@ static void check_typos(const char *arg, const struct option *options) } } +static void parse_options_check(const struct option *opts) +{ + int err = 0; + + for (; opts->type != OPTION_END; opts++) { + if ((opts->flags & PARSE_OPT_LASTARG_DEFAULT) && + (opts->flags & PARSE_OPT_OPTARG)) { + if (opts->long_name) { + error("`--%s` uses incompatible flags " + "LASTARG_DEFAULT and OPTARG", opts->long_name); + } else { + error("`-%c` uses incompatible flags " + "LASTARG_DEFAULT and OPTARG", opts->short_name); + } + err |= 1; + } + } + + if (err) + exit(129); +} + void parse_options_start(struct parse_opt_ctx_t *ctx, - int argc, const char **argv, int flags) + int argc, const char **argv, const char *prefix, + int flags) { memset(ctx, 0, sizeof(*ctx)); ctx->argc = argc - 1; ctx->argv = argv + 1; ctx->out = argv; + ctx->prefix = prefix; ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0); ctx->flags = flags; + if ((flags & PARSE_OPT_KEEP_UNKNOWN) && + (flags & PARSE_OPT_STOP_AT_NON_OPTION)) + die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together"); } static int usage_with_options_internal(const char * const *, @@ -253,6 +351,10 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, const struct option *options, const char * const usagestr[]) { + int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP); + + parse_options_check(options); + /* we must reset ->opt, unknown short option leave it dangling */ ctx->opt = NULL; @@ -260,6 +362,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, const char *arg = ctx->argv[0]; if (*arg != '-' || !arg[1]) { + if (parse_nodash_opt(ctx, arg, options) == 0) + continue; if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION) break; ctx->out[ctx->cpidx++] = ctx->argv[0]; @@ -268,18 +372,18 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, if (arg[1] != '-') { ctx->opt = arg + 1; - if (*ctx->opt == 'h') + if (internal_help && *ctx->opt == 'h') return parse_options_usage(usagestr, options); switch (parse_short_opt(ctx, options)) { case -1: return parse_options_usage(usagestr, options); case -2: - return PARSE_OPT_UNKNOWN; + goto unknown; } if (ctx->opt) check_typos(arg + 1, options); while (ctx->opt) { - if (*ctx->opt == 'h') + if (internal_help && *ctx->opt == 'h') return parse_options_usage(usagestr, options); switch (parse_short_opt(ctx, options)) { case -1: @@ -292,7 +396,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, */ ctx->argv[0] = xstrdup(ctx->opt - 1); *(char *)ctx->argv[0] = '-'; - return PARSE_OPT_UNKNOWN; + goto unknown; } } continue; @@ -306,16 +410,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx, break; } - if (!strcmp(arg + 2, "help-all")) + if (internal_help && !strcmp(arg + 2, "help-all")) return usage_with_options_internal(usagestr, options, 1); - if (!strcmp(arg + 2, "help")) + if (internal_help && !strcmp(arg + 2, "help")) return parse_options_usage(usagestr, options); switch (parse_long_opt(ctx, arg + 2, options)) { case -1: return parse_options_usage(usagestr, options); case -2: - return PARSE_OPT_UNKNOWN; + goto unknown; } + continue; +unknown: + if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN)) + return PARSE_OPT_UNKNOWN; + ctx->out[ctx->cpidx++] = ctx->argv[0]; + ctx->opt = NULL; } return PARSE_OPT_DONE; } @@ -327,12 +437,13 @@ int parse_options_end(struct parse_opt_ctx_t *ctx) return ctx->cpidx + ctx->argc; } -int parse_options(int argc, const char **argv, const struct option *options, - const char * const usagestr[], int flags) +int parse_options(int argc, const char **argv, const char *prefix, + const struct option *options, const char * const usagestr[], + int flags) { struct parse_opt_ctx_t ctx; - parse_options_start(&ctx, argc, argv, flags); + parse_options_start(&ctx, argc, argv, prefix, flags); switch (parse_options_step(&ctx, options, usagestr)) { case PARSE_OPT_HELP: exit(129); @@ -350,12 +461,29 @@ int parse_options(int argc, const char **argv, const struct option *options, return parse_options_end(&ctx); } +static int usage_argh(const struct option *opts) +{ + const char *s; + int literal = (opts->flags & PARSE_OPT_LITERAL_ARGHELP) || !opts->argh; + if (opts->flags & PARSE_OPT_OPTARG) + if (opts->long_name) + s = literal ? "[=%s]" : "[=<%s>]"; + else + s = literal ? "[%s]" : "[<%s>]"; + else + s = literal ? " %s" : " <%s>"; + return fprintf(stderr, s, opts->argh ? opts->argh : "..."); +} + #define USAGE_OPTS_WIDTH 24 #define USAGE_GAP 2 int usage_with_options_internal(const char * const *usagestr, const struct option *opts, int full) { + if (!usagestr) + return PARSE_OPT_HELP; + fprintf(stderr, "usage: %s\n", *usagestr++); while (*usagestr && **usagestr) fprintf(stderr, " or: %s\n", *usagestr++); @@ -383,51 +511,21 @@ int usage_with_options_internal(const char * const *usagestr, continue; pos = fprintf(stderr, " "); - if (opts->short_name) - pos += fprintf(stderr, "-%c", opts->short_name); + if (opts->short_name) { + if (opts->flags & PARSE_OPT_NODASH) + pos += fprintf(stderr, "%c", opts->short_name); + else + pos += fprintf(stderr, "-%c", opts->short_name); + } if (opts->long_name && opts->short_name) pos += fprintf(stderr, ", "); if (opts->long_name) pos += fprintf(stderr, "--%s", opts->long_name); + if (opts->type == OPTION_NUMBER) + pos += fprintf(stderr, "-NUM"); - switch (opts->type) { - case OPTION_ARGUMENT: - break; - case OPTION_INTEGER: - if (opts->flags & PARSE_OPT_OPTARG) - if (opts->long_name) - pos += fprintf(stderr, "[=<n>]"); - else - pos += fprintf(stderr, "[<n>]"); - else - pos += fprintf(stderr, " <n>"); - break; - case OPTION_CALLBACK: - if (opts->flags & PARSE_OPT_NOARG) - break; - /* FALLTHROUGH */ - case OPTION_STRING: - if (opts->argh) { - if (opts->flags & PARSE_OPT_OPTARG) - if (opts->long_name) - pos += fprintf(stderr, "[=<%s>]", opts->argh); - else - pos += fprintf(stderr, "[<%s>]", opts->argh); - else - pos += fprintf(stderr, " <%s>", opts->argh); - } else { - if (opts->flags & PARSE_OPT_OPTARG) - if (opts->long_name) - pos += fprintf(stderr, "[=...]"); - else - pos += fprintf(stderr, "[...]"); - else - pos += fprintf(stderr, " ..."); - } - break; - default: /* OPTION_{BIT,BOOLEAN,SET_INT,SET_PTR} */ - break; - } + if (!(opts->flags & PARSE_OPT_NOARG)) + pos += usage_argh(opts); if (pos <= USAGE_OPTS_WIDTH) pad = USAGE_OPTS_WIDTH - pos; @@ -522,15 +620,3 @@ int parse_opt_with_commit(const struct option *opt, const char *arg, int unset) 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. - */ -extern const char *parse_options_fix_filename(const char *prefix, const char *file) -{ - if (!file || !prefix || is_absolute_path(file) || !strcmp("-", file)) - return file; - return prefix_filename(prefix, strlen(prefix), file); -} - diff --git a/parse-options.h b/parse-options.h index 912290549b..aba30671dc 100644 --- a/parse-options.h +++ b/parse-options.h @@ -6,8 +6,10 @@ enum parse_opt_type { OPTION_END, OPTION_ARGUMENT, OPTION_GROUP, + OPTION_NUMBER, /* options with no arguments */ OPTION_BIT, + OPTION_NEGBIT, OPTION_BOOLEAN, /* _INCR would have been a better name */ OPTION_SET_INT, OPTION_SET_PTR, @@ -15,12 +17,15 @@ enum parse_opt_type { OPTION_STRING, OPTION_INTEGER, OPTION_CALLBACK, + OPTION_FILENAME }; enum parse_opt_flags { PARSE_OPT_KEEP_DASHDASH = 1, PARSE_OPT_STOP_AT_NON_OPTION = 2, PARSE_OPT_KEEP_ARGV0 = 4, + PARSE_OPT_KEEP_UNKNOWN = 8, + PARSE_OPT_NO_INTERNAL_HELP = 16, }; enum parse_opt_option_flags { @@ -29,6 +34,8 @@ enum parse_opt_option_flags { PARSE_OPT_NONEG = 4, PARSE_OPT_HIDDEN = 8, PARSE_OPT_LASTARG_DEFAULT = 16, + PARSE_OPT_NODASH = 32, + PARSE_OPT_LITERAL_ARGHELP = 64, }; struct option; @@ -50,7 +57,7 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * * `argh`:: * token to explain the kind of argument this option wants. Keep it - * homogenous across the repository. + * homogeneous across the repository. * * `help`:: * the short help associated to what the option does. @@ -59,11 +66,20 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset); * * `flags`:: * mask of parse_opt_option_flags. - * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs) - * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs + * PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs) + * PARSE_OPT_NOARG: says that this option takes no argument * PARSE_OPT_NONEG: says that this option cannot be negated - * PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in - * the long one. + * PARSE_OPT_HIDDEN: this option is skipped in the default usage, and + * shown only in the full usage. + * PARSE_OPT_LASTARG_DEFAULT: says that this option will take the default + * value if no argument is given when the option + * is last on the command line. If the option is + * not last it will require an argument. + * Should not be used with PARSE_OPT_OPTARG. + * PARSE_OPT_NODASH: this option doesn't start with a dash. + * PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets + * (i.e. '<argh>') in the help message. + * Useful for options with multiple parameters. * * `callback`:: * pointer to the callback to use for OPTION_CALLBACK. @@ -88,32 +104,44 @@ struct option { }; #define OPT_END() { OPTION_END } -#define OPT_ARGUMENT(l, h) { OPTION_ARGUMENT, 0, (l), NULL, NULL, (h) } +#define OPT_ARGUMENT(l, h) { OPTION_ARGUMENT, 0, (l), NULL, NULL, \ + (h), PARSE_OPT_NOARG} #define OPT_GROUP(h) { OPTION_GROUP, 0, NULL, NULL, NULL, (h) } -#define OPT_BIT(s, l, v, h, b) { OPTION_BIT, (s), (l), (v), NULL, (h), 0, NULL, (b) } -#define OPT_BOOLEAN(s, l, v, h) { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) } -#define OPT_SET_INT(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, (h), 0, NULL, (i) } -#define OPT_SET_PTR(s, l, v, h, p) { OPTION_SET_PTR, (s), (l), (v), NULL, (h), 0, NULL, (p) } -#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), NULL, (h) } +#define OPT_BIT(s, l, v, h, b) { OPTION_BIT, (s), (l), (v), NULL, (h), \ + PARSE_OPT_NOARG, NULL, (b) } +#define OPT_NEGBIT(s, l, v, h, b) { OPTION_NEGBIT, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, NULL, (b) } +#define OPT_BOOLEAN(s, l, v, h) { OPTION_BOOLEAN, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG } +#define OPT_SET_INT(s, l, v, h, i) { OPTION_SET_INT, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, NULL, (i) } +#define OPT_SET_PTR(s, l, v, h, p) { OPTION_SET_PTR, (s), (l), (v), NULL, \ + (h), PARSE_OPT_NOARG, NULL, (p) } +#define OPT_INTEGER(s, l, v, h) { OPTION_INTEGER, (s), (l), (v), "n", (h) } #define OPT_STRING(s, l, v, a, h) { OPTION_STRING, (s), (l), (v), (a), (h) } #define OPT_DATE(s, l, v, h) \ { OPTION_CALLBACK, (s), (l), (v), "time",(h), 0, \ parse_opt_approxidate_cb } #define OPT_CALLBACK(s, l, v, a, h, f) \ { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) } +#define OPT_NUMBER_CALLBACK(v, h, f) \ + { OPTION_NUMBER, 0, NULL, (v), NULL, (h), \ + PARSE_OPT_NOARG | PARSE_OPT_NONEG, (f) } +#define OPT_FILENAME(s, l, v, h) { OPTION_FILENAME, (s), (l), (v), \ + "FILE", (h) } /* parse_options() will filter out the processed options and leave the - * non-option argments in argv[]. + * non-option arguments in argv[]. * Returns the number of arguments left in argv[]. */ -extern int parse_options(int argc, const char **argv, +extern int parse_options(int argc, const char **argv, const char *prefix, const struct option *options, const char * const usagestr[], int flags); extern NORETURN void usage_with_options(const char * const *usagestr, const struct option *options); -/*----- incremantal advanced APIs -----*/ +/*----- incremental advanced APIs -----*/ enum { PARSE_OPT_HELP = -1, @@ -132,13 +160,15 @@ struct parse_opt_ctx_t { int argc, cpidx; const char *opt; int flags; + const char *prefix; }; extern int parse_options_usage(const char * const *usagestr, const struct option *opts); extern void parse_options_start(struct parse_opt_ctx_t *ctx, - int argc, const char **argv, int flags); + int argc, const char **argv, const char *prefix, + int flags); extern int parse_options_step(struct parse_opt_ctx_t *ctx, const struct option *options, @@ -166,6 +196,4 @@ extern int parse_opt_with_commit(const struct option *, const char *, int); "use <n> digits to display SHA-1s", \ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 } -extern const char *parse_options_fix_filename(const char *prefix, const char *file); - #endif diff --git a/patch-delta.c b/patch-delta.c index ed9db81fa8..ef748ce96d 100644 --- a/patch-delta.c +++ b/patch-delta.c @@ -44,7 +44,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size, if (cmd & 0x01) cp_off = *data++; if (cmd & 0x02) cp_off |= (*data++ << 8); if (cmd & 0x04) cp_off |= (*data++ << 16); - if (cmd & 0x08) cp_off |= (*data++ << 24); + if (cmd & 0x08) cp_off |= ((unsigned) *data++ << 24); if (cmd & 0x10) cp_size = *data++; if (cmd & 0x20) cp_size |= (*data++ << 8); if (cmd & 0x40) cp_size |= (*data++ << 16); diff --git a/patch-ids.c b/patch-ids.c index 3be5d3165e..5717257051 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -1,6 +1,7 @@ #include "cache.h" #include "diff.h" #include "commit.h" +#include "sha1-lookup.h" #include "patch-ids.h" static int commit_patch_id(struct commit *commit, struct diff_options *options, @@ -15,99 +16,15 @@ static int commit_patch_id(struct commit *commit, struct diff_options *options, return diff_flush_patch_id(options, sha1); } -static uint32_t take2(const unsigned char *id) +static const unsigned char *patch_id_access(size_t index, void *table) { - return ((id[0] << 8) | id[1]); + struct patch_id **id_table = table; + return id_table[index]->patch_id; } -/* - * Conventional binary search loop looks like this: - * - * do { - * int mi = (lo + hi) / 2; - * int cmp = "entry pointed at by mi" minus "target"; - * if (!cmp) - * return (mi is the wanted one) - * if (cmp > 0) - * hi = mi; "mi is larger than target" - * else - * lo = mi+1; "mi is smaller than target" - * } while (lo < hi); - * - * The invariants are: - * - * - When entering the loop, lo points at a slot that is never - * above the target (it could be at the target), hi points at a - * slot that is guaranteed to be above the target (it can never - * be at the target). - * - * - We find a point 'mi' between lo and hi (mi could be the same - * as lo, but never can be the same as hi), and check if it hits - * the target. There are three cases: - * - * - if it is a hit, we are happy. - * - * - if it is strictly higher than the target, we update hi with - * it. - * - * - if it is strictly lower than the target, we update lo to be - * one slot after it, because we allow lo to be at the target. - * - * When choosing 'mi', we do not have to take the "middle" but - * anywhere in between lo and hi, as long as lo <= mi < hi is - * satisfied. When we somehow know that the distance between the - * target and lo is much shorter than the target and hi, we could - * pick mi that is much closer to lo than the midway. - */ static int patch_pos(struct patch_id **table, int nr, const unsigned char *id) { - int hi = nr; - int lo = 0; - int mi = 0; - - if (!nr) - return -1; - - if (nr != 1) { - unsigned lov, hiv, miv, ofs; - - for (ofs = 0; ofs < 18; ofs += 2) { - lov = take2(table[0]->patch_id + ofs); - hiv = take2(table[nr-1]->patch_id + ofs); - miv = take2(id + ofs); - if (miv < lov) - return -1; - if (hiv < miv) - return -1 - nr; - if (lov != hiv) { - /* - * At this point miv could be equal - * to hiv (but id could still be higher); - * the invariant of (mi < hi) should be - * kept. - */ - mi = (nr-1) * (miv - lov) / (hiv - lov); - if (lo <= mi && mi < hi) - break; - die("oops"); - } - } - if (18 <= ofs) - die("cannot happen -- lo and hi are identical"); - } - - do { - int cmp; - cmp = hashcmp(table[mi]->patch_id, id); - if (!cmp) - return mi; - if (cmp > 0) - hi = mi; - else - lo = mi + 1; - mi = (hi + lo) / 2; - } while (lo < hi); - return -lo-1; + return sha1_pos(id, table, nr, patch_id_access); } #define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */ @@ -139,6 +139,22 @@ int git_mkstemp(char *path, size_t len, const char *template) return mkstemp(path); } +/* git_mkstemps() - create tmp file with suffix honoring TMPDIR variable. */ +int git_mkstemps(char *path, size_t len, const char *template, int suffix_len) +{ + const char *tmp; + size_t n; + + tmp = getenv("TMPDIR"); + if (!tmp) + tmp = "/tmp"; + n = snprintf(path, len, "%s/%s", tmp, template); + if (len <= n) { + errno = ENAMETOOLONG; + return -1; + } + return mkstemps(path, suffix_len); +} int validate_headref(const char *path) { diff --git a/perl/Git.pm b/perl/Git.pm index 7d7f2b1d36..e8df55d2f2 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -56,7 +56,7 @@ require Exporter; @EXPORT_OK = qw(command command_oneline command_noisy command_output_pipe command_input_pipe command_close_pipe command_bidi_pipe command_close_bidi_pipe - version exec_path hash_object git_cmd_try + version exec_path html_path hash_object git_cmd_try remote_refs temp_acquire temp_release temp_reset temp_path); @@ -185,7 +185,7 @@ sub repository { if ($dir) { $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir; - $opts{Repository} = $dir; + $opts{Repository} = abs_path($dir); # If --git-dir went ok, this shouldn't die either. my $prefix = $search->command_oneline('rev-parse', '--show-prefix'); @@ -492,6 +492,16 @@ C<git --exec-path>). Useful mostly only internally. sub exec_path { command_oneline('--exec-path') } +=item html_path () + +Return path to the Git html documentation (the same as +C<git --html-path>). Useful mostly only internally. + +=cut + +sub html_path { command_oneline('--html-path') } + + =item repo_path () Return path to the git repository. Must be called on a repository instance. @@ -1270,6 +1280,8 @@ sub _cmd_exec { my ($self, @args) = @_; if ($self) { $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path(); + $self->repo_path() and $self->wc_path() + and $ENV{'GIT_WORK_TREE'} = $self->wc_path(); $self->wc_path() and chdir($self->wc_path()); $self->wc_subdir() and chdir($self->wc_subdir()); } @@ -10,6 +10,15 @@ static char *user_format; +static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat) +{ + free(user_format); + user_format = xstrdup(cp); + if (is_tformat) + rev->use_terminator = 1; + rev->commit_format = CMIT_FMT_USERFORMAT; +} + void get_commit_format(const char *arg, struct rev_info *rev) { int i; @@ -33,12 +42,7 @@ void get_commit_format(const char *arg, struct rev_info *rev) return; } if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) { - const char *cp = strchr(arg, ':') + 1; - free(user_format); - user_format = xstrdup(cp); - if (arg[0] == 't') - rev->use_terminator = 1; - rev->commit_format = CMIT_FMT_USERFORMAT; + save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't'); return; } for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) { @@ -50,6 +54,10 @@ void get_commit_format(const char *arg, struct rev_info *rev) return; } } + if (strchr(arg, '%')) { + save_user_format(rev, arg, 1); + return; + } die("invalid --pretty format: %s", arg); } @@ -127,7 +135,6 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, int namelen; unsigned long time; int tz; - const char *filler = " "; if (fmt == CMIT_FMT_ONELINE) return; @@ -146,7 +153,6 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, while (line < name_tail && isspace(name_tail[-1])) name_tail--; display_name_length = name_tail - line; - filler = ""; strbuf_addstr(sb, "From: "); add_rfc2047(sb, line, display_name_length, encoding); strbuf_add(sb, name_tail, namelen - display_name_length); @@ -154,7 +160,7 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb, } else { strbuf_addf(sb, "%s: %.*s%.*s\n", what, (fmt == CMIT_FMT_FULLER) ? 4 : 0, - filler, namelen, line); + " ", namelen, line); } switch (fmt) { case CMIT_FMT_MEDIUM: @@ -278,7 +284,7 @@ static char *replace_encoding_header(char *buf, const char *encoding) static char *logmsg_reencode(const struct commit *commit, const char *output_encoding) { - static const char *utf8 = "utf-8"; + static const char *utf8 = "UTF-8"; const char *use_encoding; char *encoding; char *out; @@ -487,6 +493,40 @@ static void parse_commit_header(struct format_commit_context *context) context->commit_header_parsed = 1; } +static int istitlechar(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '.' || c == '_'; +} + +static void format_sanitized_subject(struct strbuf *sb, const char *msg) +{ + size_t trimlen; + size_t start_len = sb->len; + int space = 2; + + for (; *msg && *msg != '\n'; msg++) { + if (istitlechar(*msg)) { + if (space == 1) + strbuf_addch(sb, '-'); + space = 0; + strbuf_addch(sb, *msg); + if (*msg == '.') + while (*(msg+1) == '.') + msg++; + } else + space |= 1; + } + + /* trim any trailing '.' or '-' characters */ + trimlen = 0; + while (sb->len - trimlen > start_len && + (sb->buf[sb->len - 1 - trimlen] == '.' + || sb->buf[sb->len - 1 - trimlen] == '-')) + trimlen++; + strbuf_remove(sb, sb->len - trimlen, trimlen); +} + const char *format_subject(struct strbuf *sb, const char *msg, const char *line_separator) { @@ -567,16 +607,16 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, return end - placeholder + 1; } if (!prefixcmp(placeholder + 1, "red")) { - strbuf_addstr(sb, "\033[31m"); + strbuf_addstr(sb, GIT_COLOR_RED); return 4; } else if (!prefixcmp(placeholder + 1, "green")) { - strbuf_addstr(sb, "\033[32m"); + strbuf_addstr(sb, GIT_COLOR_GREEN); return 6; } else if (!prefixcmp(placeholder + 1, "blue")) { - strbuf_addstr(sb, "\033[34m"); + strbuf_addstr(sb, GIT_COLOR_BLUE); return 5; } else if (!prefixcmp(placeholder + 1, "reset")) { - strbuf_addstr(sb, "\033[m"); + strbuf_addstr(sb, GIT_COLOR_RESET); return 6; } else return 0; @@ -677,6 +717,9 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder, case 's': /* subject */ format_subject(sb, msg + c->subject_off, " "); return 1; + case 'f': /* sanitized subject */ + format_sanitized_subject(sb, msg + c->subject_off); + return 1; case 'b': /* body */ strbuf_addstr(sb, msg + c->body_off); return 1; @@ -838,7 +881,7 @@ char *reencode_commit_message(const struct commit *commit, const char **encoding ? git_log_output_encoding : git_commit_encoding); if (!encoding) - encoding = "utf-8"; + encoding = "UTF-8"; if (encoding_p) *encoding_p = encoding; return logmsg_reencode(commit, encoding); diff --git a/progress.c b/progress.c index 55a8687ad1..621c34edc2 100644 --- a/progress.c +++ b/progress.c @@ -121,13 +121,13 @@ static void throughput_string(struct throughput *tp, off_t total, (int)(total >> 30), (int)(total & ((1 << 30) - 1)) / 10737419); } else if (total > 1 << 20) { + int x = total + 5243; /* for rounding */ l -= snprintf(tp->display, l, ", %u.%2.2u MiB", - (int)(total >> 20), - ((int)(total & ((1 << 20) - 1)) * 100) >> 20); + x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20); } else if (total > 1 << 10) { + int x = total + 5; /* for rounding */ l -= snprintf(tp->display, l, ", %u.%2.2u KiB", - (int)(total >> 10), - ((int)(total & ((1 << 10) - 1)) * 100) >> 10); + x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); } else { l -= snprintf(tp->display, l, ", %u bytes", (int)total); } @@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) } } -char *sq_dequote(char *arg) +char *sq_dequote_step(char *arg, char **next) { char *dst = arg; char *src = arg; @@ -92,6 +92,8 @@ char *sq_dequote(char *arg) switch (*++src) { case '\0': *dst = 0; + if (next) + *next = NULL; return arg; case '\\': c = *++src; @@ -101,11 +103,40 @@ char *sq_dequote(char *arg) } /* Fallthrough */ default: - return NULL; + if (!next || !isspace(*src)) + return NULL; + do { + c = *++src; + } while (isspace(c)); + *dst = 0; + *next = src; + return arg; } } } +char *sq_dequote(char *arg) +{ + return sq_dequote_step(arg, NULL); +} + +int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc) +{ + char *next = arg; + + if (!*arg) + return 0; + do { + char *dequoted = sq_dequote_step(next, &next); + if (!dequoted) + return -1; + ALLOC_GROW(*argv, *nr + 1, *alloc); + (*argv)[(*nr)++] = dequoted; + } while (next); + + return 0; +} + /* 1 means: quote as octal * 0 means: quote as octal if (quote_path_fully) * -1 means: never quote @@ -39,6 +39,15 @@ extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen); */ extern char *sq_dequote(char *); +/* + * Same as the above, but can be used to unwrap many arguments in the + * same string separated by space. "next" is changed to point to the + * next argument that should be passed as first parameter. When there + * is no more argument to be dequoted, "next" is updated to point to NULL. + */ +extern char *sq_dequote_step(char *arg, char **next); +extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc); + extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp); extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq); extern void quote_two_c_style(struct strbuf *, const char *, const char *, int); diff --git a/read-cache.c b/read-cache.c index 940ec76fdf..3f587110cb 100644 --- a/read-cache.c +++ b/read-cache.c @@ -67,8 +67,10 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n */ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st) { - ce->ce_ctime = st->st_ctime; - ce->ce_mtime = st->st_mtime; + ce->ce_ctime.sec = (unsigned int)st->st_ctime; + ce->ce_mtime.sec = (unsigned int)st->st_mtime; + ce->ce_ctime.nsec = ST_CTIME_NSEC(*st); + ce->ce_mtime.nsec = ST_MTIME_NSEC(*st); ce->ce_dev = st->st_dev; ce->ce_ino = st->st_ino; ce->ce_uid = st->st_uid; @@ -196,11 +198,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) default: die("internal error: ce_mode is %o", ce->ce_mode); } - if (ce->ce_mtime != (unsigned int) st->st_mtime) + if (ce->ce_mtime.sec != (unsigned int)st->st_mtime) changed |= MTIME_CHANGED; - if (trust_ctime && ce->ce_ctime != (unsigned int) st->st_ctime) + if (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime) changed |= CTIME_CHANGED; +#ifdef USE_NSEC + if (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st)) + changed |= MTIME_CHANGED; + if (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st)) + changed |= CTIME_CHANGED; +#endif + if (ce->ce_uid != (unsigned int) st->st_uid || ce->ce_gid != (unsigned int) st->st_gid) changed |= OWNER_CHANGED; @@ -232,8 +241,16 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce) { return (!S_ISGITLINK(ce->ce_mode) && - istate->timestamp && - ((unsigned int)istate->timestamp) <= ce->ce_mtime); + istate->timestamp.sec && +#ifdef USE_NSEC + /* nanosecond timestamped files can also be racy! */ + (istate->timestamp.sec < ce->ce_mtime.sec || + (istate->timestamp.sec == ce->ce_mtime.sec && + istate->timestamp.nsec <= ce->ce_mtime.nsec)) +#else + istate->timestamp.sec <= ce->ce_mtime.sec +#endif + ); } int ie_match_stat(const struct index_state *istate, @@ -443,6 +460,26 @@ int remove_index_entry_at(struct index_state *istate, int pos) return 1; } +/* + * Remove all cache ententries marked for removal, that is where + * CE_REMOVE is set in ce_flags. This is much more effective than + * calling remove_index_entry_at() for each entry to be removed. + */ +void remove_marked_cache_entries(struct index_state *istate) +{ + struct cache_entry **ce_array = istate->cache; + unsigned int i, j; + + for (i = j = 0; i < istate->cache_nr; i++) { + if (ce_array[i]->ce_flags & CE_REMOVE) + remove_name_hash(ce_array[i]); + else + ce_array[j++] = ce_array[i]; + } + istate->cache_changed = 1; + istate->cache_nr = j; +} + int remove_file_from_index(struct index_state *istate, const char *path) { int pos = index_name_pos(istate, path, strlen(path)); @@ -1139,8 +1176,10 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en size_t len; const char *name; - ce->ce_ctime = ntohl(ondisk->ctime.sec); - ce->ce_mtime = ntohl(ondisk->mtime.sec); + ce->ce_ctime.sec = ntohl(ondisk->ctime.sec); + ce->ce_mtime.sec = ntohl(ondisk->mtime.sec); + ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec); + ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec); ce->ce_dev = ntohl(ondisk->dev); ce->ce_ino = ntohl(ondisk->ino); ce->ce_mode = ntohl(ondisk->mode); @@ -1206,7 +1245,8 @@ int read_index_from(struct index_state *istate, const char *path) return istate->cache_nr; errno = ENOENT; - istate->timestamp = 0; + istate->timestamp.sec = 0; + istate->timestamp.nsec = 0; fd = open(path, O_RDONLY); if (fd < 0) { if (errno == ENOENT) @@ -1258,7 +1298,9 @@ int read_index_from(struct index_state *istate, const char *path) src_offset += ondisk_ce_size(ce); dst_offset += ce_size(ce); } - istate->timestamp = st.st_mtime; + istate->timestamp.sec = st.st_mtime; + istate->timestamp.nsec = ST_MTIME_NSEC(st); + while (src_offset <= mmap_size - 20 - 8) { /* After an array of active_nr index entries, * there can be arbitrary number of extended @@ -1288,14 +1330,15 @@ unmap: int is_index_unborn(struct index_state *istate) { - return (!istate->cache_nr && !istate->alloc && !istate->timestamp); + return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec); } int discard_index(struct index_state *istate) { istate->cache_nr = 0; istate->cache_changed = 0; - istate->timestamp = 0; + istate->timestamp.sec = 0; + istate->timestamp.nsec = 0; istate->name_hash_initialized = 0; free_hash(&istate->name_hash); cache_tree_free(&(istate->cache_tree)); @@ -1441,10 +1484,10 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce) struct ondisk_cache_entry *ondisk = xcalloc(1, size); char *name; - ondisk->ctime.sec = htonl(ce->ce_ctime); - ondisk->ctime.nsec = 0; - ondisk->mtime.sec = htonl(ce->ce_mtime); - ondisk->mtime.nsec = 0; + ondisk->ctime.sec = htonl(ce->ce_ctime.sec); + ondisk->mtime.sec = htonl(ce->ce_mtime.sec); + ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec); + ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec); ondisk->dev = htonl(ce->ce_dev); ondisk->ino = htonl(ce->ce_ino); ondisk->mode = htonl(ce->ce_mode); @@ -1466,13 +1509,14 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce) return ce_write(c, fd, ondisk, size); } -int write_index(const struct index_state *istate, int newfd) +int write_index(struct index_state *istate, int newfd) { git_SHA_CTX c; struct cache_header hdr; int i, err, removed, extended; struct cache_entry **cache = istate->cache; int entries = istate->cache_nr; + struct stat st; for (i = removed = extended = 0; i < entries; i++) { if (cache[i]->ce_flags & CE_REMOVE) @@ -1516,7 +1560,12 @@ int write_index(const struct index_state *istate, int newfd) if (err) return -1; } - return ce_flush(&c, newfd); + + if (ce_flush(&c, newfd) || fstat(newfd, &st)) + return -1; + istate->timestamp.sec = (unsigned int)st.st_mtime; + istate->timestamp.nsec = ST_MTIME_NSEC(st); + return 0; } /* diff --git a/reflog-walk.c b/reflog-walk.c index f751fdc8d8..5623ea6b48 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -241,8 +241,8 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit) commit->object.flags &= ~(ADDED | SEEN | SHOWN); } -void show_reflog_message(struct reflog_walk_info* info, int oneline, - int relative_date) +void show_reflog_message(struct reflog_walk_info *info, int oneline, + enum date_mode dmode) { if (info && info->last_commit_reflog) { struct commit_reflog *commit_reflog = info->last_commit_reflog; @@ -251,8 +251,10 @@ void show_reflog_message(struct reflog_walk_info* info, int oneline, info = &commit_reflog->reflogs->items[commit_reflog->recno+1]; if (oneline) { printf("%s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || relative_date) - printf("%s", show_date(info->timestamp, 0, 1)); + if (commit_reflog->flag || dmode) + printf("%s", show_date(info->timestamp, + info->tz, + dmode)); else printf("%d", commit_reflog->reflogs->nr - 2 - commit_reflog->recno); @@ -260,10 +262,10 @@ void show_reflog_message(struct reflog_walk_info* info, int oneline, } else { printf("Reflog: %s@{", commit_reflog->reflogs->ref); - if (commit_reflog->flag || relative_date) + if (commit_reflog->flag || dmode) printf("%s", show_date(info->timestamp, info->tz, - relative_date)); + dmode)); else printf("%d", commit_reflog->reflogs->nr - 2 - commit_reflog->recno); diff --git a/reflog-walk.h b/reflog-walk.h index 7ca1438f4d..74c90964bd 100644 --- a/reflog-walk.h +++ b/reflog-walk.h @@ -1,11 +1,14 @@ #ifndef REFLOG_WALK_H #define REFLOG_WALK_H +#include "cache.h" + extern void init_reflog_walk(struct reflog_walk_info** info); extern int add_reflog_for_walk(struct reflog_walk_info *info, struct commit *commit, const char *name); extern void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit); -extern void show_reflog_message(struct reflog_walk_info *info, int, int); +extern void show_reflog_message(struct reflog_walk_info *info, int, + enum date_mode); #endif @@ -647,19 +647,24 @@ int for_each_ref(each_ref_fn fn, void *cb_data) return do_for_each_ref("refs/", fn, 0, 0, cb_data); } +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data); +} + int for_each_tag_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data); + return for_each_ref_in("refs/tags/", fn, cb_data); } int for_each_branch_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data); + return for_each_ref_in("refs/heads/", fn, cb_data); } int for_each_remote_ref(each_ref_fn fn, void *cb_data) { - return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data); + return for_each_ref_in("refs/remotes/", fn, cb_data); } int for_each_rawref(each_ref_fn fn, void *cb_data) @@ -676,12 +681,14 @@ int for_each_rawref(each_ref_fn fn, void *cb_data) * - it has double dots "..", or * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or * - it ends with a "/". + * - it ends with ".lock" + * - it contains a "\" (backslash) */ static inline int bad_ref_char(int ch) { if (((unsigned) ch) <= ' ' || - ch == '~' || ch == '^' || ch == ':') + ch == '~' || ch == '^' || ch == ':' || ch == '\\') return 1; /* 2.13 Pattern Matching Notation */ if (ch == '?' || ch == '[') /* Unsupported */ @@ -693,7 +700,8 @@ static inline int bad_ref_char(int ch) int check_ref_format(const char *ref) { - int ch, level, bad_type; + int ch, level, bad_type, last; + int ret = CHECK_REF_FORMAT_OK; const char *cp = ref; level = 0; @@ -709,33 +717,49 @@ int check_ref_format(const char *ref) return CHECK_REF_FORMAT_ERROR; bad_type = bad_ref_char(ch); if (bad_type) { - return (bad_type == 2 && !*cp) - ? CHECK_REF_FORMAT_WILDCARD - : CHECK_REF_FORMAT_ERROR; + if (bad_type == 2 && (!*cp || *cp == '/') && + ret == CHECK_REF_FORMAT_OK) + ret = CHECK_REF_FORMAT_WILDCARD; + else + return CHECK_REF_FORMAT_ERROR; } + last = ch; /* scan the rest of the path component */ while ((ch = *cp++) != 0) { bad_type = bad_ref_char(ch); - if (bad_type) { - return (bad_type == 2 && !*cp) - ? CHECK_REF_FORMAT_WILDCARD - : CHECK_REF_FORMAT_ERROR; - } + if (bad_type) + return CHECK_REF_FORMAT_ERROR; if (ch == '/') break; - if (ch == '.' && *cp == '.') + if (last == '.' && ch == '.') + return CHECK_REF_FORMAT_ERROR; + if (last == '@' && ch == '{') return CHECK_REF_FORMAT_ERROR; + last = ch; } level++; if (!ch) { + if (ref <= cp - 2 && cp[-2] == '.') + return CHECK_REF_FORMAT_ERROR; if (level < 2) return CHECK_REF_FORMAT_ONELEVEL; - return CHECK_REF_FORMAT_OK; + if (has_extension(ref, ".lock")) + return CHECK_REF_FORMAT_ERROR; + return ret; } } } +const char *prettify_refname(const char *name) +{ + return name + ( + !prefixcmp(name, "refs/heads/") ? 11 : + !prefixcmp(name, "refs/tags/") ? 10 : + !prefixcmp(name, "refs/remotes/") ? 13 : + 0); +} + const char *ref_rev_parse_rules[] = { "%.*s", "refs/%.*s", @@ -869,8 +893,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char * name is a proper prefix of our refname. */ if (missing && - !is_refname_available(ref, NULL, get_packed_refs(), 0)) + !is_refname_available(ref, NULL, get_packed_refs(), 0)) { + last_errno = ENOTDIR; goto error_return; + } lock->lk = xcalloc(1, sizeof(struct lock_file)); @@ -978,12 +1004,10 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) } else { path = git_path("%s", refname); } - err = unlink(path); - if (err && errno != ENOENT) { + err = unlink_or_warn(path); + if (err && errno != ENOENT) ret = 1; - error("unlink(%s) failed: %s", - path, strerror(errno)); - } + if (!(delopt & REF_NODEREF)) lock->lk->filename[i] = '.'; } @@ -993,10 +1017,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt) */ ret |= repack_without_ref(refname); - err = unlink(git_path("logs/%s", lock->ref_name)); - if (err && errno != ENOENT) - fprintf(stderr, "warning: unlink(%s) failed: %s", - git_path("logs/%s", lock->ref_name), strerror(errno)); + unlink_or_warn(git_path("logs/%s", lock->ref_name)); invalidate_cached_refs(); unlock_ref(lock); return ret; @@ -1357,7 +1378,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master, if (adjust_shared_perm(git_HEAD)) { error("Unable to fix permissions on %s", lockpath); error_unlink_return: - unlink(lockpath); + unlink_or_warn(lockpath); error_free_return: free(git_HEAD); return -1; @@ -1437,8 +1458,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * if (get_sha1_hex(rec + 41, sha1)) die("Log %s is corrupt.", logfile); if (hashcmp(logged_sha1, sha1)) { - fprintf(stderr, - "warning: Log %s has gap after %s.\n", + warning("Log %s has gap after %s.", logfile, show_date(date, tz, DATE_RFC2822)); } } @@ -1450,8 +1470,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char * if (get_sha1_hex(rec + 41, logged_sha1)) die("Log %s is corrupt.", logfile); if (hashcmp(logged_sha1, sha1)) { - fprintf(stderr, - "warning: Log %s unexpectedly ended on %s.\n", + warning("Log %s unexpectedly ended on %s.", logfile, show_date(date, tz, DATE_RFC2822)); } } @@ -1628,10 +1647,121 @@ int update_ref(const char *action, const char *refname, return 0; } -struct ref *find_ref_by_name(struct ref *list, const char *name) +struct ref *find_ref_by_name(const struct ref *list, const char *name) { for ( ; list; list = list->next) if (!strcmp(list->name, name)) - return list; + return (struct ref *)list; return NULL; } + +/* + * generate a format suitable for scanf from a ref_rev_parse_rules + * rule, that is replace the "%.*s" spec with a "%s" spec + */ +static void gen_scanf_fmt(char *scanf_fmt, const char *rule) +{ + char *spec; + + spec = strstr(rule, "%.*s"); + if (!spec || strstr(spec + 4, "%.*s")) + die("invalid rule in ref_rev_parse_rules: %s", rule); + + /* copy all until spec */ + strncpy(scanf_fmt, rule, spec - rule); + scanf_fmt[spec - rule] = '\0'; + /* copy new spec */ + strcat(scanf_fmt, "%s"); + /* copy remaining rule */ + strcat(scanf_fmt, spec + 4); + + return; +} + +char *shorten_unambiguous_ref(const char *ref, int strict) +{ + int i; + static char **scanf_fmts; + static int nr_rules; + char *short_name; + + /* pre generate scanf formats from ref_rev_parse_rules[] */ + if (!nr_rules) { + size_t total_len = 0; + + /* the rule list is NULL terminated, count them first */ + for (; ref_rev_parse_rules[nr_rules]; nr_rules++) + /* no +1 because strlen("%s") < strlen("%.*s") */ + total_len += strlen(ref_rev_parse_rules[nr_rules]); + + scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len); + + total_len = 0; + for (i = 0; i < nr_rules; i++) { + scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + + total_len; + gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]); + total_len += strlen(ref_rev_parse_rules[i]); + } + } + + /* bail out if there are no rules */ + if (!nr_rules) + return xstrdup(ref); + + /* buffer for scanf result, at most ref must fit */ + short_name = xstrdup(ref); + + /* skip first rule, it will always match */ + for (i = nr_rules - 1; i > 0 ; --i) { + int j; + int rules_to_fail = i; + int short_name_len; + + if (1 != sscanf(ref, scanf_fmts[i], short_name)) + continue; + + short_name_len = strlen(short_name); + + /* + * in strict mode, all (except the matched one) rules + * must fail to resolve to a valid non-ambiguous ref + */ + if (strict) + rules_to_fail = nr_rules; + + /* + * check if the short name resolves to a valid ref, + * but use only rules prior to the matched one + */ + for (j = 0; j < rules_to_fail; j++) { + const char *rule = ref_rev_parse_rules[j]; + unsigned char short_objectname[20]; + char refname[PATH_MAX]; + + /* skip matched rule */ + if (i == j) + continue; + + /* + * the short name is ambiguous, if it resolves + * (with this previous rule) to a valid ref + * read_ref() returns 0 on success + */ + mksnpath(refname, sizeof(refname), + rule, short_name_len, short_name); + if (!read_ref(refname, short_objectname)) + break; + } + + /* + * short name is non-ambiguous if all previous rules + * haven't resolved to a valid ref + */ + if (j == rules_to_fail) + return short_name; + } + + free(short_name); + return xstrdup(ref); +} @@ -20,6 +20,7 @@ struct ref_lock { typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern int head_ref(each_ref_fn, void *); extern int for_each_ref(each_ref_fn, void *); +extern int for_each_ref_in(const char *, each_ref_fn, void *); extern int for_each_tag_ref(each_ref_fn, void *); extern int for_each_branch_ref(each_ref_fn, void *); extern int for_each_remote_ref(each_ref_fn, void *); @@ -79,6 +80,9 @@ extern int for_each_reflog(each_ref_fn, void *); #define CHECK_REF_FORMAT_WILDCARD (-3) extern int check_ref_format(const char *target); +extern const char *prettify_refname(const char *refname); +extern char *shorten_unambiguous_ref(const char *ref, int strict); + /** rename ref, return 0 on success **/ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg); @@ -5,13 +5,14 @@ #include "diff.h" #include "revision.h" #include "dir.h" +#include "tag.h" static struct refspec s_tag_refspec = { 0, 1, 0, - "refs/tags/", - "refs/tags/" + "refs/tags/*", + "refs/tags/*" }; const struct refspec *tag_refspec = &s_tag_refspec; @@ -365,7 +366,7 @@ static int handle_config(const char *key, const char *value, void *cb) } subkey = strrchr(name, '.'); if (!subkey) - return error("Config with no key for remote %s", name); + return 0; remote = make_remote(name, subkey - name); remote->origin = REMOTE_CONFIG; if (!strcmp(subkey, ".mirror")) @@ -454,16 +455,11 @@ static void read_config(void) */ static int verify_refname(char *name, int is_glob) { - int result, len = -1; + int result; - if (is_glob) { - len = strlen(name); - assert(name[len - 1] == '/'); - name[len - 1] = '\0'; - } result = check_ref_format(name); - if (is_glob) - name[len - 1] = '/'; + if (is_glob && result == CHECK_REF_FORMAT_WILDCARD) + result = CHECK_REF_FORMAT_OK; return result; } @@ -498,7 +494,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp int is_glob; const char *lhs, *rhs; - llen = is_glob = 0; + is_glob = 0; lhs = refspec[i]; if (*lhs == '+') { @@ -519,16 +515,15 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp if (rhs) { size_t rlen = strlen(++rhs); - is_glob = (2 <= rlen && !strcmp(rhs + rlen - 2, "/*")); - rs[i].dst = xstrndup(rhs, rlen - is_glob); + is_glob = (1 <= rlen && strchr(rhs, '*')); + rs[i].dst = xstrndup(rhs, rlen); } llen = (rhs ? (rhs - lhs - 1) : strlen(lhs)); - if (2 <= llen && !memcmp(lhs + llen - 2, "/*", 2)) { + if (1 <= llen && memchr(lhs, '*', llen)) { if ((rhs && !is_glob) || (!rhs && fetch)) goto invalid; is_glob = 1; - llen--; } else if (rhs && is_glob) { goto invalid; } @@ -672,6 +667,17 @@ struct remote *remote_get(const char *name) return ret; } +int remote_is_configured(const char *name) +{ + int i; + read_config(); + + for (i = 0; i < remotes_nr; i++) + if (!strcmp(name, remotes[i]->name)) + return 1; + return 0; +} + int for_each_remote(each_remote_fn fn, void *priv) { int i, result = 0; @@ -728,6 +734,41 @@ int remote_has_url(struct remote *remote, const char *url) return 0; } +static int match_name_with_pattern(const char *key, const char *name, + const char *value, char **result) +{ + const char *kstar = strchr(key, '*'); + size_t klen; + size_t ksuffixlen; + size_t namelen; + int ret; + if (!kstar) + die("Key '%s' of pattern had no '*'", key); + klen = kstar - key; + ksuffixlen = strlen(kstar + 1); + namelen = strlen(name); + ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen && + !memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen); + if (ret && value) { + const char *vstar = strchr(value, '*'); + size_t vlen; + size_t vsuffixlen; + if (!vstar) + die("Value '%s' of pattern has no '*'", value); + vlen = vstar - value; + vsuffixlen = strlen(vstar + 1); + *result = xmalloc(vlen + vsuffixlen + + strlen(name) - + klen - ksuffixlen + 1); + strncpy(*result, value, vlen); + strncpy(*result + vlen, + name + klen, namelen - klen - ksuffixlen); + strcpy(*result + vlen + namelen - klen - ksuffixlen, + vstar + 1); + } + return ret; +} + int remote_find_tracking(struct remote *remote, struct refspec *refspec) { int find_src = refspec->src == NULL; @@ -751,13 +792,7 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec) if (!fetch->dst) continue; if (fetch->pattern) { - if (!prefixcmp(needle, key)) { - *result = xmalloc(strlen(value) + - strlen(needle) - - strlen(key) + 1); - strcpy(*result, value); - strcpy(*result + strlen(value), - needle + strlen(key)); + if (match_name_with_pattern(key, needle, value, result)) { refspec->force = fetch->force; return 0; } @@ -787,10 +822,18 @@ struct ref *alloc_ref(const char *name) static struct ref *copy_ref(const struct ref *ref) { - struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1); - memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1); - ret->next = NULL; - return ret; + struct ref *cpy; + size_t len; + if (!ref) + return NULL; + len = strlen(ref->name); + cpy = xmalloc(sizeof(struct ref) + len + 1); + memcpy(cpy, ref, sizeof(struct ref) + len + 1); + cpy->next = NULL; + cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL; + cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL; + cpy->peer_ref = copy_ref(ref->peer_ref); + return cpy; } struct ref *copy_ref_list(const struct ref *ref) @@ -809,6 +852,7 @@ static void free_ref(struct ref *ref) { if (!ref) return; + free_ref(ref->peer_ref); free(ref->remote_status); free(ref->symref); free(ref); @@ -819,7 +863,6 @@ void free_refs(struct ref *ref) struct ref *next; while (ref) { next = ref->next; - free(ref->peer_ref); free_ref(ref); ref = next; } @@ -936,6 +979,7 @@ static int match_explicit(struct ref *src, struct ref *dst, struct refspec *rs) { struct ref *matched_src, *matched_dst; + int copy_src; const char *dst_value = rs->dst; char *dst_guess; @@ -946,6 +990,7 @@ static int match_explicit(struct ref *src, struct ref *dst, matched_src = matched_dst = NULL; switch (count_refspec_match(rs->src, src, &matched_src)) { case 1: + copy_src = 1; break; case 0: /* The source could be in the get_sha1() format @@ -955,6 +1000,7 @@ static int match_explicit(struct ref *src, struct ref *dst, matched_src = try_explicit_object_name(rs->src); if (!matched_src) return error("src refspec %s does not match any.", rs->src); + copy_src = 0; break; default: return error("src refspec %s matches more than one.", rs->src); @@ -1000,7 +1046,7 @@ static int match_explicit(struct ref *src, struct ref *dst, return error("dst ref %s receives from more than one src.", matched_dst->name); else { - matched_dst->peer_ref = matched_src; + matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src; matched_dst->force = rs->force; } return 0; @@ -1029,7 +1075,8 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, continue; } - if (rs[i].pattern && !prefixcmp(src->name, rs[i].src)) + if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name, + NULL, NULL)) return rs + i; } if (matching_refs != -1) @@ -1038,26 +1085,35 @@ static const struct refspec *check_pattern_match(const struct refspec *rs, return NULL; } +static struct ref **tail_ref(struct ref **head) +{ + struct ref **tail = head; + while (*tail) + tail = &((*tail)->next); + return tail; +} + /* * Note. This is used only by "push"; refspec matching rules for * push and fetch are subtly different, so do not try to reuse it * without thinking. */ -int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, +int match_refs(struct ref *src, struct ref **dst, int nr_refspec, const char **refspec, int flags) { struct refspec *rs; int send_all = flags & MATCH_REFS_ALL; int send_mirror = flags & MATCH_REFS_MIRROR; + int errs; static const char *default_refspec[] = { ":", 0 }; + struct ref **dst_tail = tail_ref(dst); if (!nr_refspec) { nr_refspec = 1; refspec = default_refspec; } rs = parse_push_refspec(nr_refspec, (const char **) refspec); - if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec)) - return -1; + errs = match_explicit_refs(src, *dst, &dst_tail, rs, nr_refspec); /* pick the remainder */ for ( ; src; src = src->next) { @@ -1083,13 +1139,11 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, } else { const char *dst_side = pat->dst ? pat->dst : pat->src; - dst_name = xmalloc(strlen(dst_side) + - strlen(src->name) - - strlen(pat->src) + 2); - strcpy(dst_name, dst_side); - strcat(dst_name, src->name + strlen(pat->src)); + if (!match_name_with_pattern(pat->src, src->name, + dst_side, &dst_name)) + die("Didn't think it matches any more"); } - dst_peer = find_ref_by_name(dst, dst_name); + dst_peer = find_ref_by_name(*dst, dst_name); if (dst_peer) { if (dst_peer->peer_ref) /* We're already sending something to this ref. */ @@ -1105,14 +1159,16 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, goto free_name; /* Create a new one and link it */ - dst_peer = make_linked_ref(dst_name, dst_tail); + dst_peer = make_linked_ref(dst_name, &dst_tail); hashcpy(dst_peer->new_sha1, src->new_sha1); } - dst_peer->peer_ref = src; + dst_peer->peer_ref = copy_ref(src); dst_peer->force = pat->force; free_name: free(dst_name); } + if (errs) + return -1; return 0; } @@ -1134,8 +1190,9 @@ struct branch *branch_get(const char *name) for (i = 0; i < ret->merge_nr; i++) { ret->merge[i] = xcalloc(1, sizeof(**ret->merge)); ret->merge[i]->src = xstrdup(ret->merge_name[i]); - remote_find_tracking(ret->remote, - ret->merge[i]); + if (remote_find_tracking(ret->remote, ret->merge[i]) + && !strcmp(ret->remote_name, ".")) + ret->merge[i]->dst = xstrdup(ret->merge_name[i]); } } } @@ -1163,19 +1220,17 @@ static struct ref *get_expanded_map(const struct ref *remote_refs, struct ref *ret = NULL; struct ref **tail = &ret; - int remote_prefix_len = strlen(refspec->src); - int local_prefix_len = strlen(refspec->dst); + char *expn_name; for (ref = remote_refs; ref; ref = ref->next) { if (strchr(ref->name, '^')) continue; /* a dereference item */ - if (!prefixcmp(ref->name, refspec->src)) { - const char *match; + if (match_name_with_pattern(refspec->src, ref->name, + refspec->dst, &expn_name)) { struct ref *cpy = copy_ref(ref); - match = ref->name + remote_prefix_len; - cpy->peer_ref = alloc_ref_with_prefix(refspec->dst, - local_prefix_len, match); + cpy->peer_ref = alloc_ref(expn_name); + free(expn_name); if (refspec->force) cpy->peer_ref->force = 1; *tail = cpy; @@ -1278,6 +1333,54 @@ int resolve_remote_symref(struct ref *ref, struct ref *list) return 1; } +static void unmark_and_free(struct commit_list *list, unsigned int mark) +{ + while (list) { + struct commit_list *temp = list; + temp->item->object.flags &= ~mark; + list = temp->next; + free(temp); + } +} + +int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1) +{ + struct object *o; + struct commit *old, *new; + struct commit_list *list, *used; + int found = 0; + + /* Both new and old must be commit-ish and new is descendant of + * old. Otherwise we require --force. + */ + o = deref_tag(parse_object(old_sha1), NULL, 0); + if (!o || o->type != OBJ_COMMIT) + return 0; + old = (struct commit *) o; + + o = deref_tag(parse_object(new_sha1), NULL, 0); + if (!o || o->type != OBJ_COMMIT) + return 0; + new = (struct commit *) o; + + if (parse_commit(new) < 0) + return 0; + + used = list = NULL; + commit_list_insert(new, &list); + while (list) { + new = pop_most_recent_commit(&list, TMP_MARK); + commit_list_insert(new, &used); + if (new == old) { + found = 1; + break; + } + } + unmark_and_free(list, TMP_MARK); + unmark_and_free(used, TMP_MARK); + return found; +} + /* * Return true if there is anything to report, otherwise false. */ @@ -1305,13 +1408,13 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs) base = branch->merge[0]->dst; if (!resolve_ref(base, sha1, 1, NULL)) return 0; - theirs = lookup_commit(sha1); + theirs = lookup_commit_reference(sha1); if (!theirs) return 0; if (!resolve_ref(branch->refname, sha1, 1, NULL)) return 0; - ours = lookup_commit(sha1); + ours = lookup_commit_reference(sha1); if (!ours) return 0; @@ -1366,9 +1469,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) return 0; base = branch->merge[0]->dst; - if (!prefixcmp(base, "refs/remotes/")) { - base += strlen("refs/remotes/"); - } + base = shorten_unambiguous_ref(base, 0); if (!num_theirs) strbuf_addf(sb, "Your branch is ahead of '%s' " "by %d commit%s.\n", @@ -1385,3 +1486,68 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb) base, num_ours, num_theirs); return 1; } + +static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) +{ + struct ref ***local_tail = cb_data; + struct ref *ref; + int len; + + /* we already know it starts with refs/ to get here */ + if (check_ref_format(refname + 5)) + return 0; + + len = strlen(refname) + 1; + ref = xcalloc(1, sizeof(*ref) + len); + hashcpy(ref->new_sha1, sha1); + memcpy(ref->name, refname, len); + **local_tail = ref; + *local_tail = &ref->next; + return 0; +} + +struct ref *get_local_heads(void) +{ + struct ref *local_refs = NULL, **local_tail = &local_refs; + for_each_ref(one_local_ref, &local_tail); + return local_refs; +} + +struct ref *guess_remote_head(const struct ref *head, + const struct ref *refs, + int all) +{ + const struct ref *r; + struct ref *list = NULL; + struct ref **tail = &list; + + if (!head) + return NULL; + + /* + * Some transports support directly peeking at + * where HEAD points; if that is the case, then + * we don't have to guess. + */ + if (head->symref) + return copy_ref(find_ref_by_name(refs, head->symref)); + + /* If refs/heads/master could be right, it is. */ + if (!all) { + r = find_ref_by_name(refs, "refs/heads/master"); + if (r && !hashcmp(r->old_sha1, head->old_sha1)) + return copy_ref(r); + } + + /* Look for another ref that points there */ + for (r = refs; r; r = r->next) { + if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) { + *tail = copy_ref(r); + tail = &((*tail)->next); + if (!all) + break; + } + } + + return list; +} @@ -45,6 +45,7 @@ struct remote { }; struct remote *remote_get(const char *name); +int remote_is_configured(const char *name); typedef int each_remote_fn(struct remote *remote, void *priv); int for_each_remote(each_remote_fn fn, void *priv); @@ -74,6 +75,7 @@ int check_ref_type(const struct ref *ref, int flags); void free_refs(struct ref *ref); int resolve_remote_symref(struct ref *ref, struct ref *list); +int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1); /* * Removes and frees any duplicate refs in the map. @@ -83,7 +85,7 @@ void ref_remove_duplicates(struct ref *ref_map); int valid_fetch_refspec(const char *refspec); struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec); -int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, +int match_refs(struct ref *src, struct ref **dst, int nr_refspec, const char **refspec, int all); /* @@ -137,4 +139,15 @@ enum match_refs_flags { int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs); int format_tracking_info(struct branch *branch, struct strbuf *sb); +struct ref *get_local_heads(void); +/* + * Find refs from a list which are likely to be pointed to by the given HEAD + * ref. If 'all' is false, returns the most likely ref; otherwise, returns a + * list of all candidate refs. If no match is found (or 'head' is NULL), + * returns NULL. All returns are newly allocated and should be freed. + */ +struct ref *guess_remote_head(const struct ref *head, + const struct ref *refs, + int all); + #endif @@ -12,15 +12,15 @@ static int rerere_autoupdate; static char *merge_rr_path; -static const char *rr_path(const char *name, const char *file) +const char *rerere_path(const char *hex, const char *file) { - return git_path("rr-cache/%s/%s", name, file); + return git_path("rr-cache/%s/%s", hex, file); } -static int has_resolution(const char *name) +int has_rerere_resolution(const char *hex) { struct stat st; - return !stat(rr_path(name, "postimage"), &st); + return !stat(rerere_path(hex, "postimage"), &st); } static void read_rr(struct string_list *rr) @@ -173,7 +173,7 @@ static int handle_file(const char *path, git_SHA1_Final(sha1, &ctx); if (hunk != RR_CONTEXT) { if (output) - unlink(output); + unlink_or_warn(output); return error("Could not parse conflict hunks in %s", path); } if (wrerror) @@ -208,12 +208,12 @@ static int merge(const char *name, const char *path) mmbuffer_t result = {NULL, 0}; xpparam_t xpp = {XDF_NEED_MINIMAL}; - if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0) + if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0) return 1; - if (read_mmfile(&cur, rr_path(name, "thisimage")) || - read_mmfile(&base, rr_path(name, "preimage")) || - read_mmfile(&other, rr_path(name, "postimage"))) + if (read_mmfile(&cur, rerere_path(name, "thisimage")) || + read_mmfile(&base, rerere_path(name, "preimage")) || + read_mmfile(&other, rerere_path(name, "postimage"))) return 1; ret = xdl_merge(&base, &cur, "", &other, "", &xpp, XDL_MERGE_ZEALOUS, &result); @@ -291,7 +291,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) string_list_insert(path, rr)->util = hex; if (mkdir(git_path("rr-cache/%s", hex), 0755)) continue; - handle_file(path, NULL, rr_path(hex, "preimage")); + handle_file(path, NULL, rerere_path(hex, "preimage")); fprintf(stderr, "Recorded preimage for '%s'\n", path); } } @@ -307,7 +307,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) const char *path = rr->items[i].string; const char *name = (const char *)rr->items[i].util; - if (has_resolution(name)) { + if (has_rerere_resolution(name)) { if (!merge(name, path)) { if (rerere_autoupdate) string_list_insert(path, &update); @@ -326,7 +326,7 @@ static int do_plain_rerere(struct string_list *rr, int fd) continue; fprintf(stderr, "Recorded resolution for '%s'.\n", path); - copy_file(rr_path(name, "postimage"), path, 0666); + copy_file(rerere_path(name, "postimage"), path, 0666); mark_resolved: rr->items[i].util = NULL; } @@ -5,5 +5,7 @@ extern int setup_rerere(struct string_list *); extern int rerere(void); +extern const char *rerere_path(const char *hex, const char *file); +extern int has_rerere_resolution(const char *hex); #endif diff --git a/revision.c b/revision.c index d7d345bdd9..bf58448367 100644 --- a/revision.c +++ b/revision.c @@ -209,7 +209,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object } /* - * Tree object? Either mark it uniniteresting, or add it + * Tree object? Either mark it uninteresting, or add it * to the list of objects to look at later.. */ if (object->type == OBJ_TREE) { @@ -256,10 +256,12 @@ static int everybody_uninteresting(struct commit_list *orig) /* * The goal is to get REV_TREE_NEW as the result only if the - * diff consists of all '+' (and no other changes), and - * REV_TREE_DIFFERENT otherwise (of course if the trees are - * the same we want REV_TREE_SAME). That means that once we - * get to REV_TREE_DIFFERENT, we do not have to look any further. + * diff consists of all '+' (and no other changes), REV_TREE_OLD + * if the whole diff is removal of old data, and otherwise + * REV_TREE_DIFFERENT (of course if the trees are the same we + * want REV_TREE_SAME). + * That means that once we get to REV_TREE_DIFFERENT, we do not + * have to look any further. */ static int tree_difference = REV_TREE_SAME; @@ -268,22 +270,9 @@ static void file_add_remove(struct diff_options *options, const unsigned char *sha1, const char *fullpath) { - int diff = REV_TREE_DIFFERENT; + int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD; - /* - * Is it an add of a new file? It means that the old tree - * didn't have it at all, so we will turn "REV_TREE_SAME" -> - * "REV_TREE_NEW", but leave any "REV_TREE_DIFFERENT" alone - * (and if it already was "REV_TREE_NEW", we'll keep it - * "REV_TREE_NEW" of course). - */ - if (addremove == '+') { - diff = tree_difference; - if (diff != REV_TREE_SAME) - return; - diff = REV_TREE_NEW; - } - tree_difference = diff; + tree_difference |= diff; if (tree_difference == REV_TREE_DIFFERENT) DIFF_OPT_SET(options, HAS_CHANGES); } @@ -305,6 +294,8 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct if (!t1) return REV_TREE_NEW; + if (!t2) + return REV_TREE_OLD; if (revs->simplify_by_decoration) { /* @@ -323,8 +314,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct if (!revs->prune_data) return REV_TREE_SAME; } - if (!t2) - return REV_TREE_DIFFERENT; + tree_difference = REV_TREE_SAME; DIFF_OPT_CLR(&revs->pruning, HAS_CHANGES); if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", @@ -429,6 +419,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) p->parents = NULL; } /* fallthrough */ + case REV_TREE_OLD: case REV_TREE_DIFFERENT: tree_changed = 1; pp = &parent->next; @@ -1130,9 +1121,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if (!strcmp(arg, "--pretty")) { revs->verbose_header = 1; get_commit_format(arg+8, revs); - } else if (!prefixcmp(arg, "--pretty=")) { + } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) { revs->verbose_header = 1; get_commit_format(arg+9, revs); + } else if (!strcmp(arg, "--oneline")) { + revs->verbose_header = 1; + get_commit_format("oneline", revs); + revs->abbrev_commit = 1; } else if (!strcmp(arg, "--graph")) { revs->topo_order = 1; revs->rewrite_parents = 1; diff --git a/revision.h b/revision.h index bfe27071bf..227164cf70 100644 --- a/revision.h +++ b/revision.h @@ -85,8 +85,10 @@ struct rev_info { struct log_info *loginfo; int nr, total; const char *mime_boundary; + const char *patch_suffix; + int numbered_files; char *message_id; - const char *ref_message_id; + struct string_list *ref_message_ids; const char *add_signoff; const char *extra_headers; const char *log_reencode; @@ -116,8 +118,9 @@ struct rev_info { }; #define REV_TREE_SAME 0 -#define REV_TREE_NEW 1 -#define REV_TREE_DIFFERENT 2 +#define REV_TREE_NEW 1 /* Only new files */ +#define REV_TREE_OLD 2 /* Only files removed */ +#define REV_TREE_DIFFERENT 3 /* Mixed changes */ /* revision.c */ void read_revisions_from_stdin(struct rev_info *revs); diff --git a/run-command.c b/run-command.c index b05c734d05..eb2efc3307 100644 --- a/run-command.c +++ b/run-command.c @@ -106,7 +106,7 @@ int start_command(struct child_process *cmd) if (cmd->env) { for (; *cmd->env; cmd->env++) { if (strchr(*cmd->env, '=')) - putenv((char*)*cmd->env); + putenv((char *)*cmd->env); else unsetenv(*cmd->env); } diff --git a/send-pack.h b/send-pack.h index 8ff1dc3539..1d7b1b3b4f 100644 --- a/send-pack.h +++ b/send-pack.h @@ -2,17 +2,16 @@ #define SEND_PACK_H struct send_pack_args { - const char *receivepack; unsigned verbose:1, - send_all:1, send_mirror:1, force_update:1, use_thin_pack:1, + use_ofs_delta:1, dry_run:1; }; int send_pack(struct send_pack_args *args, - const char *dest, struct remote *remote, - int nr_heads, const char **heads); + int fd[], struct child_process *conn, + struct ref *remote_refs, struct extra_have_objects *extra_have); #endif diff --git a/server-info.c b/server-info.c index 66b0d9d878..4098ca2b5c 100644 --- a/server-info.c +++ b/server-info.c @@ -132,8 +132,8 @@ static int read_pack_info_file(const char *infofile) static int compare_info(const void *a_, const void *b_) { - struct pack_info * const* a = a_; - struct pack_info * const* b = b_; + struct pack_info *const *a = a_; + struct pack_info *const *b = b_; if (0 <= (*a)->old_num && 0 <= (*b)->old_num) /* Keep the order in the original */ @@ -246,7 +246,7 @@ int update_server_info(int force) errs = errs | update_info_packs(force); /* remove leftover rev-cache file if there is any */ - unlink(git_path("info/rev-cache")); + unlink_or_warn(git_path("info/rev-cache")); return errs; } diff --git a/sha1-lookup.c b/sha1-lookup.c index da357479cf..c4dc55d1f5 100644 --- a/sha1-lookup.c +++ b/sha1-lookup.c @@ -1,6 +1,107 @@ #include "cache.h" #include "sha1-lookup.h" +static uint32_t take2(const unsigned char *sha1) +{ + return ((sha1[0] << 8) | sha1[1]); +} + +/* + * Conventional binary search loop looks like this: + * + * do { + * int mi = (lo + hi) / 2; + * int cmp = "entry pointed at by mi" minus "target"; + * if (!cmp) + * return (mi is the wanted one) + * if (cmp > 0) + * hi = mi; "mi is larger than target" + * else + * lo = mi+1; "mi is smaller than target" + * } while (lo < hi); + * + * The invariants are: + * + * - When entering the loop, lo points at a slot that is never + * above the target (it could be at the target), hi points at a + * slot that is guaranteed to be above the target (it can never + * be at the target). + * + * - We find a point 'mi' between lo and hi (mi could be the same + * as lo, but never can be the same as hi), and check if it hits + * the target. There are three cases: + * + * - if it is a hit, we are happy. + * + * - if it is strictly higher than the target, we update hi with + * it. + * + * - if it is strictly lower than the target, we update lo to be + * one slot after it, because we allow lo to be at the target. + * + * When choosing 'mi', we do not have to take the "middle" but + * anywhere in between lo and hi, as long as lo <= mi < hi is + * satisfied. When we somehow know that the distance between the + * target and lo is much shorter than the target and hi, we could + * pick mi that is much closer to lo than the midway. + */ +/* + * The table should contain "nr" elements. + * The sha1 of element i (between 0 and nr - 1) should be returned + * by "fn(i, table)". + */ +int sha1_pos(const unsigned char *sha1, void *table, size_t nr, + sha1_access_fn fn) +{ + size_t hi = nr; + size_t lo = 0; + size_t mi = 0; + + if (!nr) + return -1; + + if (nr != 1) { + size_t lov, hiv, miv, ofs; + + for (ofs = 0; ofs < 18; ofs += 2) { + lov = take2(fn(0, table) + ofs); + hiv = take2(fn(nr - 1, table) + ofs); + miv = take2(sha1 + ofs); + if (miv < lov) + return -1; + if (hiv < miv) + return -1 - nr; + if (lov != hiv) { + /* + * At this point miv could be equal + * to hiv (but sha1 could still be higher); + * the invariant of (mi < hi) should be + * kept. + */ + mi = (nr - 1) * (miv - lov) / (hiv - lov); + if (lo <= mi && mi < hi) + break; + die("BUG: assertion failed in binary search"); + } + } + if (18 <= ofs) + die("cannot happen -- lo and hi are identical"); + } + + do { + int cmp; + cmp = hashcmp(fn(mi, table), sha1); + if (!cmp) + return mi; + if (cmp > 0) + hi = mi; + else + lo = mi + 1; + mi = (hi + lo) / 2; + } while (lo < hi); + return -lo-1; +} + /* * Conventional binary search loop looks like this: * diff --git a/sha1-lookup.h b/sha1-lookup.h index 3249a81b3d..20af285681 100644 --- a/sha1-lookup.h +++ b/sha1-lookup.h @@ -1,6 +1,13 @@ #ifndef SHA1_LOOKUP_H #define SHA1_LOOKUP_H +typedef const unsigned char *sha1_access_fn(size_t index, void *table); + +extern int sha1_pos(const unsigned char *sha1, + void *table, + size_t nr, + sha1_access_fn fn); + extern int sha1_entry_pos(const void *table, size_t elem_size, size_t key_offset, diff --git a/sha1_file.c b/sha1_file.c index 8fe135dc61..8f5fe62d54 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -720,6 +720,8 @@ static int open_packed_git_1(struct packed_git *p) return error("packfile %s index unavailable", p->pack_name); p->pack_fd = open(p->pack_name, O_RDONLY); + while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1)) + p->pack_fd = open(p->pack_name, O_RDONLY); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) return -1; @@ -791,7 +793,7 @@ static int in_window(struct pack_window *win, off_t offset) && (offset + 20) <= (win_off + win->len); } -unsigned char* use_pack(struct packed_git *p, +unsigned char *use_pack(struct packed_git *p, struct pack_window **w_cursor, off_t offset, unsigned int *left) @@ -937,6 +939,8 @@ static void prepare_packed_git_one(char *objdir, int local) sprintf(path, "%s/pack", objdir); len = strlen(path); dir = opendir(path); + while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1)) + dir = opendir(path); if (!dir) { if (errno != ENOENT) error("unable to open object pack directory: %s: %s", @@ -1158,8 +1162,7 @@ unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) { unsigned shift; - unsigned char c; - unsigned long size; + unsigned long size, c; unsigned long used = 0; c = buf[used++]; @@ -2225,7 +2228,9 @@ int move_temp_to_file(const char *tmpfile, const char *filename) { int ret = 0; - if (link(tmpfile, filename)) + if (object_creation_mode == OBJECT_CREATION_USES_RENAMES) + goto try_rename; + else if (link(tmpfile, filename)) ret = errno; /* @@ -2240,11 +2245,12 @@ int move_temp_to_file(const char *tmpfile, const char *filename) * left to unlink. */ if (ret && ret != EEXIST) { + try_rename: if (!rename(tmpfile, filename)) goto out; ret = errno; } - unlink(tmpfile); + unlink_or_warn(tmpfile); if (ret) { if (ret != EEXIST) { return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret)); @@ -2336,6 +2342,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, filename = sha1_file_name(sha1); fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); + while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1)) + fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); if (fd < 0) { if (errno == EACCES) return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); diff --git a/sha1_name.c b/sha1_name.c index 2f75179f4c..904bcd96a5 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -242,10 +242,10 @@ static int ambiguous_path(const char *path, int len) * *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) +static char *substitute_branch_name(const char **string, int *len) { struct strbuf buf = STRBUF_INIT; - int ret = interpret_nth_last_branch(*string, &buf); + int ret = interpret_branch_name(*string, &buf); if (ret == *len) { size_t size; @@ -259,7 +259,7 @@ static char *substitute_nth_last_branch(const char **string, int *len) int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) { - char *last_branch = substitute_nth_last_branch(&str, &len); + char *last_branch = substitute_branch_name(&str, &len); const char **p, *r; int refs_found = 0; @@ -288,7 +288,7 @@ int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref) int dwim_log(const char *str, int len, unsigned char *sha1, char **log) { - char *last_branch = substitute_nth_last_branch(&str, &len); + char *last_branch = substitute_branch_name(&str, &len); const char **p; int logs_found = 0; @@ -355,7 +355,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1) struct strbuf buf = STRBUF_INIT; int ret; /* try the @{-N} syntax for n-th checkout */ - ret = interpret_nth_last_branch(str+at, &buf); + ret = interpret_branch_name(str+at, &buf); if (ret > 0) { /* substitute this branch name and restart */ return get_sha1_1(buf.buf, buf.len, sha1); @@ -750,7 +750,7 @@ static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1, * 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) +int interpret_branch_name(const char *name, struct strbuf *buf) { long nth; int i, retval; @@ -40,6 +40,7 @@ static struct commands { } cmd_list[] = { { "git-receive-pack", do_generic_cmd }, { "git-upload-pack", do_generic_cmd }, + { "git-upload-archive", do_generic_cmd }, { "cvs", do_cvs_cmd }, { NULL }, }; diff --git a/sideband.c b/sideband.c index cca3360546..899b1ff366 100644 --- a/sideband.c +++ b/sideband.c @@ -19,7 +19,7 @@ #define FIX_SIZE 10 /* large enough for any of the above */ -int recv_sideband(const char *me, int in_stream, int out, int err) +int recv_sideband(const char *me, int in_stream, int out) { unsigned pf = strlen(PREFIX); unsigned sf; @@ -41,8 +41,7 @@ int recv_sideband(const char *me, int in_stream, int out, int err) if (len == 0) break; if (len < 1) { - len = sprintf(buf, "%s: protocol error: no band designator\n", me); - safe_write(err, buf, len); + fprintf(stderr, "%s: protocol error: no band designator\n", me); return SIDEBAND_PROTOCOL_ERROR; } band = buf[pf] & 0xff; @@ -50,8 +49,8 @@ int recv_sideband(const char *me, int in_stream, int out, int err) switch (band) { case 3: buf[pf] = ' '; - buf[pf+1+len] = '\n'; - safe_write(err, buf, pf+1+len+1); + buf[pf+1+len] = '\0'; + fprintf(stderr, "%s\n", buf); return SIDEBAND_REMOTE_ERROR; case 2: buf[pf] = ' '; @@ -95,12 +94,12 @@ int recv_sideband(const char *me, int in_stream, int out, int err) memcpy(save, b + brk, sf); b[brk + sf - 1] = b[brk - 1]; memcpy(b + brk - 1, suffix, sf); - safe_write(err, b, brk + sf); + fprintf(stderr, "%.*s", brk + sf, b); memcpy(b + brk, save, sf); len -= brk; } else { int l = brk ? brk : len; - safe_write(err, b, l); + fprintf(stderr, "%.*s", l, b); len -= l; } @@ -112,10 +111,8 @@ int recv_sideband(const char *me, int in_stream, int out, int err) safe_write(out, buf + pf+1, len); continue; default: - len = sprintf(buf, - "%s: protocol error: bad band #%d\n", - me, band); - safe_write(err, buf, len); + fprintf(stderr, "%s: protocol error: bad band #%d\n", + me, band); return SIDEBAND_PROTOCOL_ERROR; } } diff --git a/sideband.h b/sideband.h index a84b6917c7..d72db35d1e 100644 --- a/sideband.h +++ b/sideband.h @@ -7,7 +7,7 @@ #define DEFAULT_PACKET_MAX 1000 #define LARGE_PACKET_MAX 65520 -int recv_sideband(const char *me, int in_stream, int out, int err); +int recv_sideband(const char *me, int in_stream, int out); ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max); #endif @@ -1,4 +1,5 @@ #include "cache.h" +#include "refs.h" int prefixcmp(const char *str, const char *prefix) { @@ -139,14 +140,11 @@ void strbuf_list_free(struct strbuf **sbs) int strbuf_cmp(const struct strbuf *a, const struct strbuf *b) { - int cmp; - if (a->len < b->len) { - cmp = memcmp(a->buf, b->buf, a->len); - return cmp ? cmp : -1; - } else { - cmp = memcmp(a->buf, b->buf, b->len); - return cmp ? cmp : a->len != b->len; - } + int len = a->len < b->len ? a->len: b->len; + int cmp = memcmp(a->buf, b->buf, len); + if (cmp) + return cmp; + return a->len < b->len ? -1: a->len != b->len; } void strbuf_splice(struct strbuf *sb, size_t pos, size_t len, @@ -360,3 +358,19 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint) return len; } + +int strbuf_branchname(struct strbuf *sb, const char *name) +{ + int len = strlen(name); + if (interpret_branch_name(name, sb) == len) + return 0; + strbuf_add(sb, name, len); + return len; +} + +int strbuf_check_branch_ref(struct strbuf *sb, const char *name) +{ + strbuf_branchname(sb, name); + strbuf_splice(sb, 0, 0, "refs/heads/", 11); + return check_ref_format(sb->buf); +} @@ -11,7 +11,7 @@ * build complex strings/buffers whose final size isn't easily known. * * It is NOT legal to copy the ->buf pointer away. - * `strbuf_detach' is the operation that detachs a buffer from its shell + * `strbuf_detach' is the operation that detaches a buffer from its shell * while keeping the shell valid wrt its invariants. * * 2. the ->buf member is a byte array that has at least ->len + 1 bytes @@ -131,4 +131,7 @@ extern int strbuf_getline(struct strbuf *, FILE *, int); extern void stripspace(struct strbuf *buf, int skip_comments); extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env); +extern int strbuf_branchname(struct strbuf *sb, const char *name); +extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name); + #endif /* STRBUF_H */ diff --git a/string-list.c b/string-list.c index 15e14cf47a..1ac536e638 100644 --- a/string-list.c +++ b/string-list.c @@ -92,6 +92,16 @@ struct string_list_item *string_list_lookup(const char *string, struct string_li return list->items + i; } +int for_each_string_list(string_list_each_func_t fn, + struct string_list *list, void *cb_data) +{ + int i, ret = 0; + for (i = 0; i < list->nr; i++) + if ((ret = fn(&list->items[i], cb_data))) + break; + return ret; +} + void string_list_clear(struct string_list *list, int free_util) { if (list->items) { diff --git a/string-list.h b/string-list.h index d32ba05202..14bbc477de 100644 --- a/string-list.h +++ b/string-list.h @@ -20,6 +20,11 @@ void string_list_clear(struct string_list *list, int free_util); 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 this function to iterate over each item */ +typedef int (*string_list_each_func_t)(struct string_list_item *, void *); +int for_each_string_list(string_list_each_func_t, + struct string_list *list, void *cb_data); + /* 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, diff --git a/symlinks.c b/symlinks.c index f262b7c44b..8dcd63261f 100644 --- a/symlinks.c +++ b/symlinks.c @@ -1,51 +1,54 @@ #include "cache.h" -static struct cache_def { - char path[PATH_MAX + 1]; - int len; - int flags; - int track_flags; - int prefix_len_stat_func; -} cache; - /* * Returns the length (on a path component basis) of the longest - * common prefix match of 'name' and the cached path string. + * common prefix match of 'name_a' and 'name_b'. */ -static inline int longest_match_lstat_cache(int len, const char *name, - int *previous_slash) +static int longest_path_match(const char *name_a, int len_a, + const char *name_b, int len_b, + int *previous_slash) { 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] == '/') { + max_len = len_a < len_b ? len_a : len_b; + while (i < max_len && name_a[i] == name_b[i]) { + if (name_a[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)) { + /* + * Is 'name_b' a substring of 'name_a', the other way around, + * or is 'name_a' and 'name_b' the exact same string? + */ + if (i >= max_len && ((len_a > len_b && name_a[len_b] == '/') || + (len_a < len_b && name_b[len_a] == '/') || + (len_a == len_b))) { match_len_prev = match_len; - match_len = len; + match_len = i; } *previous_slash = match_len_prev; return match_len; } -static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func) +static struct cache_def { + char path[PATH_MAX + 1]; + int len; + int flags; + int track_flags; + int prefix_len_stat_func; +} cache; + +static inline void reset_lstat_cache(void) { cache.path[0] = '\0'; cache.len = 0; cache.flags = 0; - cache.track_flags = track_flags; - cache.prefix_len_stat_func = prefix_len_stat_func; + /* + * The track_flags and prefix_len_stat_func members is only + * set by the safeguard rule inside lstat_cache() + */ } #define FL_DIR (1 << 0) @@ -67,7 +70,7 @@ static inline void reset_lstat_cache(int track_flags, int prefix_len_stat_func) * 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, +static int lstat_cache(const char *name, int len, int track_flags, int prefix_len_stat_func) { int match_len, last_slash, last_slash_dir, previous_slash; @@ -77,11 +80,13 @@ static int lstat_cache(int len, const char *name, 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. + * As a safeguard rule 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); + reset_lstat_cache(); + cache.track_flags = track_flags; + cache.prefix_len_stat_func = prefix_len_stat_func; match_len = last_slash = 0; } else { /* @@ -89,7 +94,8 @@ static int lstat_cache(int len, const char *name, * the 2 "excluding" path types. */ match_len = last_slash = - longest_match_lstat_cache(len, name, &previous_slash); + longest_path_match(name, len, cache.path, cache.len, + &previous_slash); match_flags = cache.flags & track_flags & (FL_NOENT|FL_SYMLINK); if (match_flags && match_len == cache.len) return match_flags; @@ -153,7 +159,7 @@ static int lstat_cache(int len, const char *name, cache.path[last_slash] = '\0'; cache.len = last_slash; cache.flags = save_flags; - } else if (track_flags & FL_DIR && + } else if ((track_flags & FL_DIR) && last_slash_dir > 0 && last_slash_dir <= PATH_MAX) { /* * We have a separate test for the directory case, @@ -170,7 +176,7 @@ static int lstat_cache(int len, const char *name, cache.len = last_slash_dir; cache.flags = FL_DIR; } else { - reset_lstat_cache(track_flags, prefix_len_stat_func); + reset_lstat_cache(); } return ret_flags; } @@ -179,19 +185,20 @@ static int lstat_cache(int len, const char *name, * Invalidate the given 'name' from the cache, if 'name' matches * completely with the cache. */ -void invalidate_lstat_cache(int len, const char *name) +void invalidate_lstat_cache(const char *name, int len) { int match_len, previous_slash; - match_len = longest_match_lstat_cache(len, name, &previous_slash); + match_len = longest_path_match(name, len, cache.path, cache.len, + &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); + } else { + reset_lstat_cache(); + } } } @@ -200,7 +207,7 @@ void invalidate_lstat_cache(int len, const char *name) */ void clear_lstat_cache(void) { - reset_lstat_cache(0, 0); + reset_lstat_cache(); } #define USE_ONLY_LSTAT 0 @@ -208,9 +215,9 @@ void clear_lstat_cache(void) /* * Return non-zero if path 'name' has a leading symlink component */ -int has_symlink_leading_path(int len, const char *name) +int has_symlink_leading_path(const char *name, int len) { - return lstat_cache(len, name, + return lstat_cache(name, len, FL_SYMLINK|FL_DIR, USE_ONLY_LSTAT) & FL_SYMLINK; } @@ -219,9 +226,9 @@ int has_symlink_leading_path(int len, const char *name) * 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) +int has_symlink_or_noent_leading_path(const char *name, int len) { - return lstat_cache(len, name, + return lstat_cache(name, len, FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT) & (FL_SYMLINK|FL_NOENT); } @@ -233,9 +240,65 @@ int has_symlink_or_noent_leading_path(int len, const char *name) * '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) +int has_dirs_only_path(const char *name, int len, int prefix_len) { - return lstat_cache(len, name, + return lstat_cache(name, len, FL_DIR|FL_FULLPATH, prefix_len) & FL_DIR; } + +static struct removal_def { + char path[PATH_MAX]; + int len; +} removal; + +static void do_remove_scheduled_dirs(int new_len) +{ + while (removal.len > new_len) { + removal.path[removal.len] = '\0'; + if (rmdir(removal.path)) + break; + do { + removal.len--; + } while (removal.len > new_len && + removal.path[removal.len] != '/'); + } + removal.len = new_len; +} + +void schedule_dir_for_removal(const char *name, int len) +{ + int match_len, last_slash, i, previous_slash; + + match_len = last_slash = i = + longest_path_match(name, len, removal.path, removal.len, + &previous_slash); + /* Find last slash inside 'name' */ + while (i < len) { + if (name[i] == '/') + last_slash = i; + i++; + } + + /* + * If we are about to go down the directory tree, we check if + * we must first go upwards the tree, such that we then can + * remove possible empty directories as we go upwards. + */ + if (match_len < last_slash && match_len < removal.len) + do_remove_scheduled_dirs(match_len); + /* + * If we go deeper down the directory tree, we only need to + * save the new path components as we go down. + */ + if (match_len < last_slash) { + memcpy(&removal.path[match_len], &name[match_len], + last_slash - match_len); + removal.len = last_slash; + } +} + +void remove_scheduled_dirs(void) +{ + do_remove_scheduled_dirs(0); +} diff --git a/t/Makefile b/t/Makefile index 9149373032..bf816fc850 100644 --- a/t/Makefile +++ b/t/Makefile @@ -38,4 +38,7 @@ full-svn-test: $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8 -.PHONY: pre-clean $(T) aggregate-results clean +valgrind: + GIT_TEST_OPTS=--valgrind $(MAKE) + +.PHONY: pre-clean $(T) aggregate-results clean valgrind @@ -39,7 +39,8 @@ this: * passed all 3 test(s) You can pass --verbose (or -v), --debug (or -d), and --immediate -(or -i) command line argument to the test. +(or -i) command line argument to the test, or by setting GIT_TEST_OPTS +appropriately before running "make". --verbose:: This makes the test more verbose. Specifically, the @@ -58,6 +59,21 @@ You can pass --verbose (or -v), --debug (or -d), and --immediate This causes additional long-running tests to be run (where available), for more exhaustive testing. +--valgrind:: + Execute all Git binaries with valgrind and exit with status + 126 on errors (just like regular tests, this will only stop + the test script when running under -i). Valgrind errors + go to stderr, so you might want to pass the -v option, too. + + Since it makes no sense to run the tests with --valgrind and + not see any output, this option implies --verbose. For + convenience, it also implies --tee. + +--tee:: + In addition to printing the test output to the terminal, + write it to files named 't/test-results/$TEST_NAME.out'. + As the names depend on the tests' file names, it is safe to + run the tests with this option in parallel. Skipping Tests -------------- diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index cacb273aff..396b9653a3 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -114,7 +114,10 @@ test_expect_success \ test_expect_success \ 'some edit' \ 'mv file file.orig && - sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" < file.orig > file && + { + cat file.orig && + echo + } | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" > file && echo "incomplete" | tr -d "\\012" >>file && GIT_AUTHOR_NAME="D" git commit -a -m "edit"' diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh index 67c431d4eb..5654962343 100644 --- a/t/lib-git-svn.sh +++ b/t/lib-git-svn.sh @@ -5,9 +5,12 @@ git_svn_id=git""-svn-id if test -n "$NO_SVN_TESTS" then - test_expect_success 'skipping git svn tests, NO_SVN_TESTS defined' : + say 'skipping git svn tests, NO_SVN_TESTS defined' + test_done +fi +if ! test_have_prereq PERL; then + say 'skipping git svn tests, perl not available' test_done - exit fi GIT_DIR=$PWD/.git @@ -17,13 +20,14 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree svn >/dev/null 2>&1 if test $? -ne 1 then - test_expect_success 'skipping git svn tests, svn not found' : + say 'skipping git svn tests, svn not found' test_done - exit fi svnrepo=$PWD/svnrepo export svnrepo +svnconf=$PWD/svnconf +export svnconf perl -w -e " use SVN::Core; @@ -41,9 +45,8 @@ then else err='Perl SVN libraries not found or unusable, skipping test' fi - test_expect_success "$err" : + say "$err" test_done - exit fi rawsvnrepo="$svnrepo" @@ -53,6 +56,19 @@ poke() { test-chmtime +1 "$1" } +# We need this, because we should pass empty configuration directory to +# the 'svn commit' to avoid automated property changes and other stuff +# that could be set from user's configuration files in ~/.subversion. +svn_cmd () { + [ -d "$svnconf" ] || mkdir "$svnconf" + orig_svncmd="$1"; shift + if [ -z "$orig_svncmd" ]; then + svn + return + fi + svn "$orig_svncmd" --config-dir "$svnconf" "$@" +} + for d in \ "$SVN_HTTPD_PATH" \ /usr/sbin/apache2 \ @@ -144,7 +160,6 @@ require_svnserve () { then say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)' test_done - exit fi } diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 86cdebc727..6765b08065 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -8,7 +8,6 @@ then say "skipping test, network testing disabled by default" say "(define GIT_TEST_HTTPD to enable)" test_done - exit fi HTTPD_PARA="" @@ -36,7 +35,6 @@ if ! test -x "$LIB_HTTPD_PATH" then say "skipping test, no web server found at '$LIB_HTTPD_PATH'" test_done - exit fi HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \ @@ -50,7 +48,6 @@ then then say "skipping test, at least Apache version 2 is required" test_done - exit fi LIB_HTTPD_MODULE_PATH="$DEFAULT_HTTPD_MODULE_PATH" @@ -94,13 +91,20 @@ prepare_httpd() { } start_httpd() { - prepare_httpd + prepare_httpd >&3 2>&4 - trap 'stop_httpd; die' EXIT + trap 'code=$?; stop_httpd; (exit $code); die' EXIT "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \ -f "$TEST_PATH/apache.conf" $HTTPD_PARA \ - -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start + -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start \ + >&3 2>&4 + if test $? -ne 0 + then + say "skipping test, web server setup failed" + trap 'die' EXIT + test_done + fi } stop_httpd() { diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index af6e5e1d6a..21aa42f1c6 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -1,15 +1,13 @@ ServerName dummy +LockFile accept.lock PidFile httpd.pid DocumentRoot www LogFormat "%h %l %u %t \"%r\" %>s %b" common CustomLog access.log common ErrorLog error.log - -<IfDefine Darwin> +<IfModule !mod_log_config.c> LoadModule log_config_module modules/mod_log_config.so - LockFile accept.lock - PidFile httpd.pid -</IfDefine> +</IfModule> <IfDefine SSL> LoadModule ssl_module modules/mod_ssl.so diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 70df15cbd8..f4ca4fc85c 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -57,6 +57,21 @@ test_expect_failure 'pretend we have a known breakage' ' test_expect_failure 'pretend we have fixed a known breakage' ' : ' +test_set_prereq HAVEIT +haveit=no +test_expect_success HAVEIT 'test runs if prerequisite is satisfied' ' + test_have_prereq HAVEIT && + haveit=yes +' +donthaveit=yes +test_expect_success DONTHAVEIT 'unmet prerequisite causes test to be skipped' ' + donthaveit=no +' +if test $haveit$donthaveit != yesyes +then + say "bug in test framework: prerequisite tags do not work reliably" + exit 1 +fi ################################################################ # Basics of the basics @@ -100,12 +115,31 @@ test_expect_success \ 'test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904' # Various types of objects +# Some filesystems do not support symblic links; on such systems +# some expected values are different mkdir path2 path3 path3/subp3 -for p in path0 path2/file2 path3/file3 path3/subp3/file3 +paths='path0 path2/file2 path3/file3 path3/subp3/file3' +for p in $paths do echo "hello $p" >$p - ln -s "hello $p" ${p}sym done +if test_have_prereq SYMLINKS +then + for p in $paths + do + ln -s "hello $p" ${p}sym + done + expectfilter=cat + expectedtree=087704a96baf1c2d1c869a8b084481e121c88b5b + expectedptree1=21ae8269cacbe57ae09138dcc3a2887f904d02b3 + expectedptree2=3c5e5399f3a333eddecce7a9b9465b63f65f51e2 +else + expectfilter='grep -v sym' + expectedtree=8e18edf7d7edcf4371a3ac6ae5f07c2641db7c46 + expectedptree1=cfb8591b2f65de8b8cc1020cd7d9e67e7793b325 + expectedptree2=ce580448f0148b985a513b693fdf7d802cacb44f +fi + test_expect_success \ 'adding various types of objects with git update-index --add.' \ 'find path* ! -type d -print | xargs git update-index --add' @@ -115,7 +149,7 @@ test_expect_success \ 'showing stage with git ls-files --stage' \ 'git ls-files --stage >current' -cat >expected <<\EOF +$expectfilter >expected <<\EOF 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0 path2/file2 @@ -127,14 +161,14 @@ cat >expected <<\EOF EOF test_expect_success \ 'validate git ls-files output for a known tree.' \ - 'diff current expected' + 'test_cmp expected current' test_expect_success \ 'writing tree out with git write-tree.' \ 'tree=$(git write-tree)' test_expect_success \ 'validate object ID for a known tree.' \ - 'test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b' + 'test "$tree" = "$expectedtree"' test_expect_success \ 'showing tree with git ls-tree' \ @@ -145,16 +179,16 @@ cat >expected <<\EOF 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe path2 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3 path3 EOF -test_expect_success \ +test_expect_success SYMLINKS \ 'git ls-tree output for a known tree.' \ - 'diff current expected' + 'test_cmp expected current' # This changed in ls-tree pathspec change -- recursive does # not show tree nodes anymore. test_expect_success \ 'showing tree with git ls-tree -r' \ 'git ls-tree -r $tree >current' -cat >expected <<\EOF +$expectfilter >expected <<\EOF 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 path2/file2 @@ -166,7 +200,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'git ls-tree -r output for a known tree.' \ - 'diff current expected' + 'test_cmp expected current' # But with -r -t we can have both. test_expect_success \ @@ -185,23 +219,23 @@ cat >expected <<\EOF 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f path3/subp3/file3 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c path3/subp3/file3sym EOF -test_expect_success \ +test_expect_success SYMLINKS \ 'git ls-tree -r output for a known tree.' \ - 'diff current expected' + 'test_cmp expected current' test_expect_success \ 'writing partial tree out with git write-tree --prefix.' \ 'ptree=$(git write-tree --prefix=path3)' test_expect_success \ 'validate object ID for a known tree.' \ - 'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3' + 'test "$ptree" = "$expectedptree1"' test_expect_success \ 'writing partial tree out with git write-tree --prefix.' \ 'ptree=$(git write-tree --prefix=path3/subp3)' test_expect_success \ 'validate object ID for a known tree.' \ - 'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2' + 'test "$ptree" = "$expectedptree2"' cat >badobjects <<EOF 100644 blob 1000000000000000000000000000000000000000 dir/file1 @@ -234,7 +268,7 @@ test_expect_success \ newtree=$(git write-tree) && test "$newtree" = "$tree"' -cat >expected <<\EOF +$expectfilter >expected <<\EOF :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym :100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M path2/file2 @@ -257,7 +291,7 @@ test_expect_success \ 'git diff-files >current && cmp -s current /dev/null' ################################################################ -P=087704a96baf1c2d1c869a8b084481e121c88b5b +P=$expectedtree test_expect_success \ 'git commit-tree records the correct tree in a commit.' \ 'commit0=$(echo NO | git commit-tree $P) && @@ -293,7 +327,7 @@ test_expect_success 'update-index D/F conflict' ' test $numpath0 = 1 ' -test_expect_success 'absolute path works as expected' ' +test_expect_success SYMLINKS 'absolute path works as expected' ' mkdir first && ln -s ../.git first/.git && mkdir second && diff --git a/t/t0004-unwritable.sh b/t/t0004-unwritable.sh index 63e1217e71..2342ac5788 100755 --- a/t/t0004-unwritable.sh +++ b/t/t0004-unwritable.sh @@ -15,7 +15,7 @@ test_expect_success setup ' ' -test_expect_success 'write-tree should notice unwritable repository' ' +test_expect_success POSIXPERM 'write-tree should notice unwritable repository' ' ( chmod a-w .git/objects .git/objects/?? && @@ -27,7 +27,7 @@ test_expect_success 'write-tree should notice unwritable repository' ' ' -test_expect_success 'commit should notice unwritable repository' ' +test_expect_success POSIXPERM 'commit should notice unwritable repository' ' ( chmod a-w .git/objects .git/objects/?? && @@ -39,7 +39,7 @@ test_expect_success 'commit should notice unwritable repository' ' ' -test_expect_success 'update-index should notice unwritable repository' ' +test_expect_success POSIXPERM 'update-index should notice unwritable repository' ' ( echo 6O >file && @@ -52,7 +52,7 @@ test_expect_success 'update-index should notice unwritable repository' ' ' -test_expect_success 'add should notice unwritable repository' ' +test_expect_success POSIXPERM 'add should notice unwritable repository' ' ( echo b >file && diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 1be7446d8d..4e72b53140 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -429,6 +429,37 @@ test_expect_success 'in-tree .gitattributes (4)' ' } ' +test_expect_success 'checkout with existing .gitattributes' ' + + git config core.autocrlf true && + git config --unset core.safecrlf && + echo ".file2 -crlfQ" | q_to_cr >> .gitattributes && + git add .gitattributes && + git commit -m initial && + echo ".file -crlfQ" | q_to_cr >> .gitattributes && + echo "contents" > .file && + git add .gitattributes .file && + git commit -m second && + + git checkout master~1 && + git checkout master && + test "$(git diff-files --raw)" = "" + +' + +test_expect_success 'checkout when deleting .gitattributes' ' + + git rm .gitattributes && + echo "contentsQ" | q_to_cr > .file2 && + git add .file2 && + git commit -m third + + git checkout master~1 && + git checkout master && + remove_cr .file2 >/dev/null + +' + test_expect_success 'invalid .gitattributes (must not crash)' ' echo "three +crlf" >>.gitattributes && diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh index e5330395fc..c7d0324374 100755 --- a/t/t0024-crlf-archive.sh +++ b/t/t0024-crlf-archive.sh @@ -28,12 +28,12 @@ test_expect_success 'tar archive' ' "$UNZIP" -v >/dev/null 2>&1 if [ $? -eq 127 ]; then - echo "Skipping ZIP test, because unzip was not found" - test_done - exit + say "Skipping ZIP test, because unzip was not found" +else + test_set_prereq UNZIP fi -test_expect_success 'zip archive' ' +test_expect_success UNZIP 'zip archive' ' git archive --format=zip HEAD >test.zip && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index e38241c80a..bbc821ef97 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -12,12 +12,14 @@ usage: test-parse-options <options> -b, --boolean get a boolean -4, --or4 bitwise-or boolean with ...0100 + --neg-or4 same as --no-or4 -i, --integer <n> get a integer -j <n> get a integer, too --set23 set integer to 23 -t <time> get timestamp of <time> -L, --length <str> get length of <str> + -F, --file <FILE> set file to <FILE> String options -s, --string <string> @@ -29,6 +31,8 @@ String options Magic arguments --quux means --quux + -NUM set integer to NUM + + same as -b Standard options --abbrev[=<n>] use <n> digits to display SHA-1s @@ -53,10 +57,12 @@ abbrev: 7 verbose: 2 quiet: no dry run: yes +file: prefix/my.file EOF test_expect_success 'short options' ' - test-parse-options -s123 -b -i 1729 -b -vv -n > output 2> output.err && + test-parse-options -s123 -b -i 1729 -b -vv -n -F my.file \ + > output 2> output.err && test_cmp expect output && test ! -s output.err ' @@ -70,11 +76,12 @@ abbrev: 10 verbose: 2 quiet: no dry run: no +file: prefix/fi.le EOF test_expect_success 'long options' ' test-parse-options --boolean --integer 1729 --boolean --string2=321 \ - --verbose --verbose --no-dry-run --abbrev=10 \ + --verbose --verbose --no-dry-run --abbrev=10 --file fi.le\ > output 2> output.err && test ! -s output.err && test_cmp expect output @@ -84,6 +91,8 @@ test_expect_success 'missing required value' ' test-parse-options -s; test $? = 129 && test-parse-options --string; + test $? = 129 && + test-parse-options --file; test $? = 129 ' @@ -96,6 +105,7 @@ abbrev: 7 verbose: 0 quiet: no dry run: no +file: (not set) arg 00: a1 arg 01: b1 arg 02: --boolean @@ -117,6 +127,7 @@ abbrev: 7 verbose: 0 quiet: no dry run: no +file: (not set) EOF test_expect_success 'unambiguously abbreviated option' ' @@ -145,6 +156,7 @@ abbrev: 7 verbose: 0 quiet: no dry run: no +file: (not set) EOF test_expect_success 'non ambiguous option (after two options it abbreviates)' ' @@ -172,6 +184,7 @@ abbrev: 7 verbose: 0 quiet: no dry run: no +file: (not set) arg 00: --quux EOF @@ -190,6 +203,7 @@ abbrev: 7 verbose: 0 quiet: yes dry run: no +file: (not set) arg 00: foo EOF @@ -210,6 +224,7 @@ abbrev: 7 verbose: 0 quiet: no dry run: no +file: (not set) EOF test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' @@ -237,6 +252,7 @@ abbrev: 7 verbose: 0 quiet: no dry run: no +file: (not set) EOF test_expect_success 'OPT_BIT() and OPT_SET_INT() work' ' @@ -245,7 +261,58 @@ test_expect_success 'OPT_BIT() and OPT_SET_INT() work' ' test_cmp expect output ' -# --or4 -# --no-or4 +test_expect_success 'OPT_NEGBIT() and OPT_SET_INT() work' ' + test-parse-options --set23 -bbbbb --neg-or4 > output 2> output.err && + test ! -s output.err && + test_cmp expect output +' + +cat > expect <<EOF +boolean: 6 +integer: 0 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: 0 +quiet: no +dry run: no +file: (not set) +EOF + +test_expect_success 'OPT_BIT() works' ' + test-parse-options -bb --or4 > output 2> output.err && + test ! -s output.err && + test_cmp expect output +' + +test_expect_success 'OPT_NEGBIT() works' ' + test-parse-options -bb --no-neg-or4 > output 2> output.err && + test ! -s output.err && + test_cmp expect output +' + +test_expect_success 'OPT_BOOLEAN() with PARSE_OPT_NODASH works' ' + test-parse-options + + + + + + > output 2> output.err && + test ! -s output.err && + test_cmp expect output +' + +cat > expect <<EOF +boolean: 0 +integer: 12345 +timestamp: 0 +string: (not set) +abbrev: 7 +verbose: 0 +quiet: no +dry run: no +file: (not set) +EOF + +test_expect_success 'OPT_NUMBER_CALLBACK() works' ' + test-parse-options -12345 > output 2> output.err && + test ! -s output.err && + test_cmp expect output +' test_done diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh index 7edf49db3c..89282ccf7a 100755 --- a/t/t0050-filesystem.sh +++ b/t/t0050-filesystem.sh @@ -8,7 +8,9 @@ auml=`printf '\xc3\xa4'` aumlcdiar=`printf '\x61\xcc\x88'` case_insensitive= -test_expect_success 'see if we expect ' ' +unibad= +no_symlinks= +test_expect_success 'see what we expect' ' test_case=test_expect_success test_unicode=test_expect_success @@ -19,7 +21,6 @@ test_expect_success 'see if we expect ' ' then test_case=test_expect_failure case_insensitive=t - say "will test on a case insensitive filesystem" fi && rm -fr junk && mkdir junk && @@ -27,13 +28,26 @@ test_expect_success 'see if we expect ' ' case "$(cd junk && echo *)" in "$aumlcdiar") test_unicode=test_expect_failure - say "will test on a unicode corrupting filesystem" + unibad=t ;; *) ;; esac && - rm -fr junk + rm -fr junk && + { + ln -s x y 2> /dev/null && + test -h y 2> /dev/null || + no_symlinks=1 + rm -f y + } ' +test "$case_insensitive" && + say "will test on a case insensitive filesystem" +test "$unibad" && + say "will test on a unicode corrupting filesystem" +test "$no_symlinks" && + say "will test on a filesystem lacking symbolic links" + if test "$case_insensitive" then test_expect_success "detection of case insensitive filesystem during repo init" ' @@ -48,6 +62,21 @@ test_expect_success "detection of case insensitive filesystem during repo init" ' fi +if test "$no_symlinks" +then +test_expect_success "detection of filesystem w/o symlink support during repo init" ' + + v=$(git config --bool core.symlinks) && + test "$v" = false +' +else +test_expect_success "detection of filesystem w/o symlink support during repo init" ' + + test_must_fail git config --bool core.symlinks || + test "$(git config --bool core.symlinks)" = true +' +fi + test_expect_success "setup case tests" ' git config core.ignorecase true && diff --git a/t/t0055-beyond-symlinks.sh b/t/t0055-beyond-symlinks.sh index b29c37a5a4..0c6ff567a1 100755 --- a/t/t0055-beyond-symlinks.sh +++ b/t/t0055-beyond-symlinks.sh @@ -4,7 +4,7 @@ test_description='update-index and add refuse to add beyond symlinks' . ./test-lib.sh -test_expect_success setup ' +test_expect_success SYMLINKS setup ' >a && mkdir b && ln -s b c && @@ -12,12 +12,12 @@ test_expect_success setup ' git update-index --add a b/d ' -test_expect_success 'update-index --add beyond symlinks' ' +test_expect_success SYMLINKS 'update-index --add beyond symlinks' ' test_must_fail git update-index --add c/d && ! ( git ls-files | grep c/d ) ' -test_expect_success 'add beyond symlinks' ' +test_expect_success SYMLINKS 'add beyond symlinks' ' test_must_fail git add c/d && ! ( git ls-files | grep c/d ) ' diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 8336114f98..53cf1f8dc4 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -7,41 +7,91 @@ test_description='Test various path utilities' . ./test-lib.sh -norm_abs() { - test_expect_success "normalize absolute: $1 => $2" \ +norm_path() { + test_expect_success $3 "normalize path: $1 => $2" \ "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'" } +# On Windows, we are using MSYS's bash, which mangles the paths. +# Absolute paths are anchored at the MSYS installation directory, +# which means that the path / accounts for this many characters: +rootoff=$(test-path-utils normalize_path_copy / | wc -c) +# Account for the trailing LF: +if test $rootoff = 2; then + rootoff= # we are on Unix +else + rootoff=$(($rootoff-1)) +fi + ancestor() { - test_expect_success "longest ancestor: $1 $2 => $3" \ - "test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'" + # We do some math with the expected ancestor length. + expected=$3 + if test -n "$rootoff" && test "x$expected" != x-1; then + expected=$(($expected+$rootoff)) + fi + test_expect_success "longest ancestor: $1 $2 => $expected" \ + "actual=\$(test-path-utils longest_ancestor_length '$1' '$2') && + test \"\$actual\" = '$expected'" } -norm_abs "" "" -norm_abs / / -norm_abs // / -norm_abs /// / -norm_abs /. / -norm_abs /./ / -norm_abs /./.. ++failed++ -norm_abs /../. ++failed++ -norm_abs /./../.// ++failed++ -norm_abs /dir/.. / -norm_abs /dir/sub/../.. / -norm_abs /dir/sub/../../.. ++failed++ -norm_abs /dir /dir -norm_abs /dir// /dir/ -norm_abs /./dir /dir -norm_abs /dir/. /dir/ -norm_abs /dir///./ /dir/ -norm_abs /dir//sub/.. /dir/ -norm_abs /dir/sub/../ /dir/ -norm_abs //dir/sub/../. /dir/ -norm_abs /dir/s1/../s2/ /dir/s2/ -norm_abs /d1/s1///s2/..//../s3/ /d1/s3/ -norm_abs /d1/s1//../s2/../../d2 /d2 -norm_abs /d1/.../d2 /d1/.../d2 -norm_abs /d1/..././../d2 /d1/d2 +# Absolute path tests must be skipped on Windows because due to path mangling +# the test program never sees a POSIX-style absolute path +case $(uname -s) in +*MINGW*) + ;; +*) + test_set_prereq POSIX + ;; +esac + +norm_path "" "" +norm_path . "" +norm_path ./ "" +norm_path ./. "" +norm_path ./.. ++failed++ +norm_path ../. ++failed++ +norm_path ./../.// ++failed++ +norm_path dir/.. "" +norm_path dir/sub/../.. "" +norm_path dir/sub/../../.. ++failed++ +norm_path dir dir +norm_path dir// dir/ +norm_path ./dir dir +norm_path dir/. dir/ +norm_path dir///./ dir/ +norm_path dir//sub/.. dir/ +norm_path dir/sub/../ dir/ +norm_path dir/sub/../. dir/ +norm_path dir/s1/../s2/ dir/s2/ +norm_path d1/s1///s2/..//../s3/ d1/s3/ +norm_path d1/s1//../s2/../../d2 d2 +norm_path d1/.../d2 d1/.../d2 +norm_path d1/..././../d2 d1/d2 + +norm_path / / POSIX +norm_path // / POSIX +norm_path /// / POSIX +norm_path /. / POSIX +norm_path /./ / POSIX +norm_path /./.. ++failed++ POSIX +norm_path /../. ++failed++ POSIX +norm_path /./../.// ++failed++ POSIX +norm_path /dir/.. / POSIX +norm_path /dir/sub/../.. / POSIX +norm_path /dir/sub/../../.. ++failed++ POSIX +norm_path /dir /dir POSIX +norm_path /dir// /dir/ POSIX +norm_path /./dir /dir POSIX +norm_path /dir/. /dir/ POSIX +norm_path /dir///./ /dir/ POSIX +norm_path /dir//sub/.. /dir/ POSIX +norm_path /dir/sub/../ /dir/ POSIX +norm_path //dir/sub/../. /dir/ POSIX +norm_path /dir/s1/../s2/ /dir/s2/ POSIX +norm_path /d1/s1///s2/..//../s3/ /d1/s3/ POSIX +norm_path /d1/s1//../s2/../../d2 /d2 POSIX +norm_path /d1/.../d2 /d1/.../d2 POSIX +norm_path /d1/..././../d2 /d1/d2 POSIX ancestor / "" -1 ancestor / / -1 @@ -80,10 +130,10 @@ ancestor /foo/bar /:/foo:/bar/ 4 ancestor /foo/bar /foo:/:/bar/ 4 ancestor /foo/bar /:/bar/:/fo 0 ancestor /foo/bar /:/bar/ 0 -ancestor /foo/bar :://foo/. 4 -ancestor /foo/bar :://foo/.:: 4 -ancestor /foo/bar //foo/./::/bar 4 -ancestor /foo/bar ::/bar -1 +ancestor /foo/bar .:/foo/. 4 +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 \ diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index 570d3729bd..f19b4a2a4a 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -157,7 +157,7 @@ test_expect_success '3-way not overwriting local changes (their side)' ' ' -test_expect_success 'funny symlink in work tree' ' +test_expect_success SYMLINKS 'funny symlink in work tree' ' git reset --hard && git checkout -b sym-b side-b && @@ -177,7 +177,7 @@ test_expect_success 'funny symlink in work tree' ' ' -test_expect_success 'funny symlink in work tree, un-unlink-able' ' +test_expect_success SYMLINKS 'funny symlink in work tree, un-unlink-able' ' rm -fr a b && git reset --hard && @@ -189,7 +189,7 @@ test_expect_success 'funny symlink in work tree, un-unlink-able' ' ' # clean-up from the above test -chmod a+w a +chmod a+w a 2>/dev/null rm -fr a b test_expect_success 'D/F setup' ' diff --git a/t/t1010-mktree.sh b/t/t1010-mktree.sh new file mode 100755 index 0000000000..9956e3ad62 --- /dev/null +++ b/t/t1010-mktree.sh @@ -0,0 +1,71 @@ +#!/bin/sh + +test_description='git mktree' + +. ./test-lib.sh + +test_expect_success setup ' + for d in a a. a0 + do + mkdir "$d" && echo "$d/one" >"$d/one" && + git add "$d" + done && + echo zero >one && + git update-index --add --info-only one && + git write-tree --missing-ok >tree.missing && + git ls-tree $(cat tree.missing) >top.missing && + git ls-tree -r $(cat tree.missing) >all.missing && + echo one >one && + git add one && + git write-tree >tree && + git ls-tree $(cat tree) >top && + git ls-tree -r $(cat tree) >all && + test_tick && + git commit -q -m one && + H=$(git rev-parse HEAD) && + git update-index --add --cacheinfo 160000 $H sub && + test_tick && + git commit -q -m two && + git rev-parse HEAD^{tree} >tree.withsub && + git ls-tree HEAD >top.withsub && + git ls-tree -r HEAD >all.withsub +' + +test_expect_success 'ls-tree piped to mktree (1)' ' + git mktree <top >actual && + test_cmp tree actual +' + +test_expect_success 'ls-tree piped to mktree (2)' ' + git mktree <top.withsub >actual && + test_cmp tree.withsub actual +' + +test_expect_success 'ls-tree output in wrong order given to mktree (1)' ' + perl -e "print reverse <>" <top | + git mktree >actual && + test_cmp tree actual +' + +test_expect_success 'ls-tree output in wrong order given to mktree (2)' ' + perl -e "print reverse <>" <top.withsub | + git mktree >actual && + test_cmp tree.withsub actual +' + +test_expect_success 'allow missing object with --missing' ' + git mktree --missing <top.missing >actual && + test_cmp tree.missing actual +' + +test_expect_failure 'mktree reads ls-tree -r output (1)' ' + git mktree <all >actual && + test_cmp tree actual +' + +test_expect_failure 'mktree reads ls-tree -r output (2)' ' + git mktree <all.withsub >actual && + test_cmp tree.withsub actual +' + +test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index fc386ba033..210e594f6f 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -126,7 +126,7 @@ test_expect_success 'no file/rev ambiguity check inside a bare repo' ' cd foo.git && git show -s HEAD ' -test_expect_success 'detection should not be fooled by a symlink' ' +test_expect_success SYMLINKS 'detection should not be fooled by a symlink' ' cd "$HERE" && rm -fr foo.git && git clone -s .git another && diff --git a/t/t1100-commit-tree-options.sh b/t/t1100-commit-tree-options.sh index 7f7fc36734..c4414ff576 100755 --- a/t/t1100-commit-tree-options.sh +++ b/t/t1100-commit-tree-options.sh @@ -40,6 +40,6 @@ test_expect_success \ test_expect_success \ 'compare commit' \ - 'diff expected commit' + 'test_cmp expected commit' test_done diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh index 3c06842d99..43ea283242 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-repo-config.sh @@ -118,7 +118,14 @@ EOF test_expect_success 'multiple unset is correct' 'cmp .git/config expect' -mv .git/config2 .git/config +cp .git/config2 .git/config + +test_expect_success '--replace-all missing value' ' + test_must_fail git config --replace-all beta.haha && + test_cmp .git/config2 .git/config +' + +rm .git/config2 test_expect_success '--replace-all' \ 'git config --replace-all beta.haha gamma' @@ -726,7 +733,7 @@ echo >>result test_expect_success '--null --get-regexp' 'cmp result expect' -test_expect_success 'symlinked configuration' ' +test_expect_success SYMLINKS 'symlinked configuration' ' ln -s notyet myconfig && GIT_CONFIG=myconfig git config test.frotz nitfol && diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 3fddc9ee78..de42d21c92 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -26,7 +26,7 @@ modebits () { for u in 002 022 do - test_expect_success "shared=1 does not clear bits preset by umask $u" ' + test_expect_success POSIXPERM "shared=1 does not clear bits preset by umask $u" ' mkdir sub && ( cd sub && umask $u && @@ -54,7 +54,7 @@ test_expect_success 'shared=all' ' test 2 = $(git config core.sharedrepository) ' -test_expect_success 'update-server-info honors core.sharedRepository' ' +test_expect_success POSIXPERM 'update-server-info honors core.sharedRepository' ' : > a1 && git add a1 && test_tick && @@ -85,7 +85,7 @@ do git config core.sharedrepository "$u" && umask 0277 && - test_expect_success "shared = $u ($y) ro" ' + test_expect_success POSIXPERM "shared = $u ($y) ro" ' rm -f .git/info/refs && git update-server-info && @@ -97,7 +97,7 @@ do ' umask 077 && - test_expect_success "shared = $u ($x) rw" ' + test_expect_success POSIXPERM "shared = $u ($x) rw" ' rm -f .git/info/refs && git update-server-info && @@ -111,7 +111,7 @@ do done -test_expect_success 'git reflog expire honors core.sharedRepository' ' +test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' ' git config core.sharedRepository group && git reflog expire --all && actual="$(ls -l .git/logs/refs/heads/master)" && @@ -126,7 +126,7 @@ test_expect_success 'git reflog expire honors core.sharedRepository' ' esac ' -test_expect_success 'forced modes' ' +test_expect_success POSIXPERM 'forced modes' ' mkdir -p templates/hooks && echo update-server-info >templates/hooks/post-update && chmod +x templates/hooks/post-update && diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index bd589268fc..54ba3df95f 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -137,7 +137,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF test_expect_success \ "verifying $m's log" \ - "diff expect .git/logs/$m" + "test_cmp expect .git/logs/$m" rm -rf .git/$m .git/logs expect test_expect_success \ @@ -168,7 +168,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000 EOF test_expect_success \ "verifying $m's log" \ - 'diff expect .git/logs/$m' + 'test_cmp expect .git/logs/$m' rm -f .git/$m .git/logs/$m expect git update-ref $m $D @@ -272,7 +272,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000 c EOF test_expect_success \ 'git commit logged updates' \ - "diff expect .git/logs/$m" + "test_cmp expect .git/logs/$m" unset h_TEST h_OTHER h_FIXED h_MERGED test_expect_success \ diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 5b24f05573..80af6b9b7e 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -70,9 +70,7 @@ test_expect_success setup ' E=`git rev-parse --verify HEAD:A/B/E` && check_fsck && - chmod +x C && - ( test "`git config --bool core.filemode`" != false || - echo executable >>C ) && + test_chmod +x C && git add C && test_tick && git commit -m dragon && L=`git rev-parse --verify HEAD` && diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh new file mode 100755 index 0000000000..c18ed8edf9 --- /dev/null +++ b/t/t1411-reflog-show.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='Test reflog display routines' +. ./test-lib.sh + +test_expect_success 'setup' ' + echo content >file && + git add file && + test_tick && + git commit -m one +' + +cat >expect <<'EOF' +Reflog: HEAD@{0} (C O Mitter <committer@example.com>) +Reflog message: commit (initial): one +EOF +test_expect_success 'log -g shows reflog headers' ' + git log -g -1 >tmp && + grep ^Reflog <tmp >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +e46513e HEAD@{0}: commit (initial): one +EOF +test_expect_success 'oneline reflog format' ' + git log -g -1 --oneline >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Reflog: HEAD@{Thu Apr 7 15:13:13 2005 -0700} (C O Mitter <committer@example.com>) +Reflog message: commit (initial): one +EOF +test_expect_success 'using @{now} syntax shows reflog date (multiline)' ' + git log -g -1 HEAD@{now} >tmp && + grep ^Reflog <tmp >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one +EOF +test_expect_success 'using @{now} syntax shows reflog date (oneline)' ' + git log -g -1 --oneline HEAD@{now} >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +Reflog: HEAD@{1112911993 -0700} (C O Mitter <committer@example.com>) +Reflog message: commit (initial): one +EOF +test_expect_success 'using --date= shows reflog date (multiline)' ' + git log -g -1 --date=raw >tmp && + grep ^Reflog <tmp >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +e46513e HEAD@{1112911993 -0700}: commit (initial): one +EOF +test_expect_success 'using --date= shows reflog date (oneline)' ' + git log -g -1 --oneline --date=raw >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 4597af0eb6..a22632f483 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -28,4 +28,71 @@ test_expect_success 'loose objects borrowed from alternate are not missing' ' ) ' +# Corruption tests follow. Make sure to remove all traces of the +# specific corruption you test afterwards, lest a later test trip over +# it. + +test_expect_success 'object with bad sha1' ' + sha=$(echo blob | git hash-object -w --stdin) && + echo $sha && + old=$(echo $sha | sed "s+^..+&/+") && + new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff && + sha="$(dirname $new)$(basename $new)" + mv .git/objects/$old .git/objects/$new && + git update-index --add --cacheinfo 100644 $sha foo && + tree=$(git write-tree) && + cmt=$(echo bogus | git commit-tree $tree) && + git update-ref refs/heads/bogus $cmt && + (git fsck 2>out; true) && + grep "$sha.*corrupt" out && + rm -f .git/objects/$new && + git update-ref -d refs/heads/bogus && + git read-tree -u --reset HEAD +' + +test_expect_success 'branch pointing to non-commit' ' + git rev-parse HEAD^{tree} > .git/refs/heads/invalid && + git fsck 2>out && + grep "not a commit" out && + git update-ref -d refs/heads/invalid +' + +cat > invalid-tag <<EOF +object ffffffffffffffffffffffffffffffffffffffff +type commit +tag invalid +tagger T A Gger <tagger@example.com> 1234567890 -0000 + +This is an invalid tag. +EOF + +test_expect_failure 'tag pointing to nonexistent' ' + tag=$(git hash-object -w --stdin < invalid-tag) && + echo $tag > .git/refs/tags/invalid && + git fsck --tags 2>out && + cat out && + grep "could not load tagged object" out && + rm .git/refs/tags/invalid +' + +cat > wrong-tag <<EOF +object $(echo blob | git hash-object -w --stdin) +type commit +tag wrong +tagger T A Gger <tagger@example.com> 1234567890 -0000 + +This is an invalid tag. +EOF + +test_expect_failure 'tag pointing to something else than its type' ' + tag=$(git hash-object -w --stdin < wrong-tag) && + echo $tag > .git/refs/tags/wrong && + git fsck --tags 2>out && + cat out && + grep "some sane error message" out && + rm .git/refs/tags/wrong +' + + + test_done diff --git a/t/t1504-ceiling-dirs.sh b/t/t1504-ceiling-dirs.sh index e377d48902..df5ad8c686 100755 --- a/t/t1504-ceiling-dirs.sh +++ b/t/t1504-ceiling-dirs.sh @@ -13,7 +13,7 @@ test_fail() { "git rev-parse --show-prefix" } -TRASH_ROOT="$(pwd)" +TRASH_ROOT="$PWD" ROOT_PARENT=$(dirname "$TRASH_ROOT") diff --git a/t/t2001-checkout-cache-clash.sh b/t/t2001-checkout-cache-clash.sh index ef007532b1..98aa73e823 100755 --- a/t/t2001-checkout-cache-clash.sh +++ b/t/t2001-checkout-cache-clash.sh @@ -59,10 +59,10 @@ test_expect_success \ 'git read-tree -m $tree1 && git checkout-index -f -a' test_debug 'show_files $tree1' -ln -s path0 path1 -test_expect_success \ +test_expect_success SYMLINKS \ 'git update-index --add a symlink.' \ - 'git update-index --add path1' + 'ln -s path0 path1 && + git update-index --add path1' test_expect_success \ 'writing tree out with git write-tree' \ 'tree3=$(git write-tree)' diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh index 71894b3743..02a4fc5d36 100755 --- a/t/t2003-checkout-cache-mkdir.sh +++ b/t/t2003-checkout-cache-mkdir.sh @@ -19,7 +19,7 @@ test_expect_success \ echo rezrov >path1/file1 && git update-index --add path0 path1/file1' -test_expect_success \ +test_expect_success SYMLINKS \ 'have symlink in place where dir is expected.' \ 'rm -fr path0 path1 && mkdir path2 && @@ -59,7 +59,7 @@ test_expect_success \ test ! -f path1/file1' # Linus fix #1 -test_expect_success \ +test_expect_success SYMLINKS \ 'use --prefix=tmp/orary/ where tmp is a symlink' \ 'rm -fr path0 path1 path2 tmp* && mkdir tmp1 tmp1/orary && @@ -71,7 +71,7 @@ test_expect_success \ test -h tmp' # Linus fix #2 -test_expect_success \ +test_expect_success SYMLINKS \ 'use --prefix=tmp/orary- where tmp is a symlink' \ 'rm -fr path0 path1 path2 tmp* && mkdir tmp1 && @@ -82,7 +82,7 @@ test_expect_success \ test -h tmp' # Linus fix #3 -test_expect_success \ +test_expect_success SYMLINKS \ 'use --prefix=tmp- where tmp-path1 is a symlink' \ 'rm -fr path0 path1 path2 tmp* && mkdir tmp1 && diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh index 39133b8c7a..36cca14d95 100755 --- a/t/t2004-checkout-cache-temp.sh +++ b/t/t2004-checkout-cache-temp.sh @@ -194,7 +194,7 @@ test_expect_success \ test $(cat ../$s1) = tree1asubdir/path5) )' -test_expect_success \ +test_expect_success SYMLINKS \ 'checkout --temp symlink' ' rm -f path* .merge_* out .git/index && ln -s b a && diff --git a/t/t2007-checkout-symlink.sh b/t/t2007-checkout-symlink.sh index 0526fce163..20f33436d0 100755 --- a/t/t2007-checkout-symlink.sh +++ b/t/t2007-checkout-symlink.sh @@ -6,6 +6,12 @@ test_description='git checkout to switch between branches with symlink<->dir' . ./test-lib.sh +if ! test_have_prereq SYMLINKS +then + say "symbolic links not supported - skipping tests" + test_done +fi + test_expect_success setup ' mkdir frotz && diff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh index 6ef2dcfd8a..2df3fdde8b 100755 --- a/t/t2100-update-cache-badpath.sh +++ b/t/t2100-update-cache-badpath.sh @@ -26,7 +26,12 @@ All of the attempts should fail. mkdir path2 path3 date >path0 -ln -s xyzzy path1 +if test_have_prereq SYMLINKS +then + ln -s xyzzy path1 +else + date > path1 +fi date >path2/file2 date >path3/file3 @@ -38,7 +43,12 @@ rm -fr path? mkdir path0 path1 date >path2 -ln -s frotz path3 +if test_have_prereq SYMLINKS +then + ln -s frotz path3 +else + date > path3 +fi date >path0/file0 date >path1/file1 diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index b2ddf5ace3..912075063b 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -80,7 +80,7 @@ test_expect_success 'change gets noticed' ' ' -test_expect_success 'replace a file with a symlink' ' +test_expect_success SYMLINKS 'replace a file with a symlink' ' rm foo && ln -s top foo && @@ -150,7 +150,7 @@ test_expect_success 'add -u resolves unmerged paths' ' echo 2 >path3 && echo 2 >path5 && git add -u && - git ls-files -s "path?" >actual && + git ls-files -s path1 path2 path3 path4 path5 path6 >actual && { echo "100644 $three 0 path1" echo "100644 $one 1 path3" diff --git a/t/t2201-add-update-typechange.sh b/t/t2201-add-update-typechange.sh index d24c7d9e5f..2e8f702452 100755 --- a/t/t2201-add-update-typechange.sh +++ b/t/t2201-add-update-typechange.sh @@ -11,7 +11,13 @@ test_expect_success setup ' _empty=$(git hash-object --stdin <xyzzy) && >yomin && >caskly && - ln -s frotz nitfol && + if test_have_prereq SYMLINKS; then + ln -s frotz nitfol && + T_letter=T + else + printf %s frotz > nitfol && + T_letter=M + fi && mkdir rezrov && >rezrov/bozbar && git add caskly xyzzy yomin nitfol rezrov/bozbar && @@ -29,7 +35,11 @@ test_expect_success modify ' >nitfol && # rezrov/bozbar disappears rm -fr rezrov && - ln -s xyzzy rezrov && + if test_have_prereq SYMLINKS; then + ln -s xyzzy rezrov + else + printf %s xyzzy > rezrov + fi && # xyzzy disappears (not a submodule) mkdir xyzzy && echo gnusto >xyzzy/bozbar && @@ -71,7 +81,7 @@ test_expect_success modify ' s/blob/000000/ } / nitfol/{ - s/ nitfol/ $_z40 T&/ + s/ nitfol/ $_z40 $T_letter&/ s/blob/100644/ } / rezrov.bozbar/{ diff --git a/t/t2300-cd-to-toplevel.sh b/t/t2300-cd-to-toplevel.sh index 293dc353b1..3b01ad2e4d 100755 --- a/t/t2300-cd-to-toplevel.sh +++ b/t/t2300-cd-to-toplevel.sh @@ -5,7 +5,7 @@ test_description='cd_to_toplevel' . ./test-lib.sh test_cd_to_toplevel () { - test_expect_success "$2" ' + test_expect_success $3 "$2" ' ( cd '"'$1'"' && . git-sh-setup && @@ -24,14 +24,14 @@ test_cd_to_toplevel repo 'at physical root' test_cd_to_toplevel repo/sub/dir 'at physical subdir' -ln -s repo symrepo -test_cd_to_toplevel symrepo 'at symbolic root' +ln -s repo symrepo 2>/dev/null +test_cd_to_toplevel symrepo 'at symbolic root' SYMLINKS -ln -s repo/sub/dir subdir-link -test_cd_to_toplevel subdir-link 'at symbolic subdir' +ln -s repo/sub/dir subdir-link 2>/dev/null +test_cd_to_toplevel subdir-link 'at symbolic subdir' SYMLINKS cd repo -ln -s sub/dir internal-link -test_cd_to_toplevel internal-link 'at internal symbolic subdir' +ln -s sub/dir internal-link 2>/dev/null +test_cd_to_toplevel internal-link 'at internal symbolic subdir' SYMLINKS test_done diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index bc0a351392..86291e8399 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh @@ -13,12 +13,18 @@ filesystem. path2/file2 - a file in a directory path3-junk - a file to confuse things path3/file3 - a file in a directory + path4 - an empty directory ' . ./test-lib.sh date >path0 -ln -s xyzzy path1 -mkdir path2 path3 +if test_have_prereq SYMLINKS +then + ln -s xyzzy path1 +else + date > path1 +fi +mkdir path2 path3 path4 date >path2/file2 date >path2-junk date >path3/file3 @@ -28,6 +34,7 @@ git update-index --add path3-junk path3/file3 cat >expected1 <<EOF expected1 expected2 +expected3 output path0 path1 @@ -35,6 +42,8 @@ path2-junk path2/file2 EOF sed -e 's|path2/file2|path2/|' <expected1 >expected2 +cat <expected2 >expected3 +echo path4/ >>expected2 test_expect_success \ 'git ls-files --others to show output.' \ @@ -42,7 +51,7 @@ test_expect_success \ test_expect_success \ 'git ls-files --others should pick up symlinks.' \ - 'diff output expected1' + 'test_cmp expected1 output' test_expect_success \ 'git ls-files --others --directory to show output.' \ @@ -51,6 +60,14 @@ test_expect_success \ test_expect_success \ 'git ls-files --others --directory should not get confused.' \ - 'diff output expected2' + 'test_cmp expected2 output' + +test_expect_success \ + 'git ls-files --others --directory --no-empty-directory to show output.' \ + 'git ls-files --others --directory --no-empty-directory >output' + +test_expect_success \ + '--no-empty-directory hides empty directory' \ + 'test_cmp expected3 output' test_done diff --git a/t/t3010-ls-files-killed-modified.sh b/t/t3010-ls-files-killed-modified.sh index ec14040637..95671c2053 100755 --- a/t/t3010-ls-files-killed-modified.sh +++ b/t/t3010-ls-files-killed-modified.sh @@ -38,7 +38,12 @@ modified without reporting path9 and path10. . ./test-lib.sh date >path0 -ln -s xyzzy path1 +if test_have_prereq SYMLINKS +then + ln -s xyzzy path1 +else + date > path1 +fi mkdir path2 path3 date >path2/file2 date >path3/file3 @@ -52,8 +57,14 @@ test_expect_success \ rm -fr path? ;# leave path10 alone date >path2 -ln -s frotz path3 -ln -s nitfol path5 +if test_have_prereq SYMLINKS +then + ln -s frotz path3 + ln -s nitfol path5 +else + date > path3 + date > path5 +fi mkdir path0 path1 path6 date >path0/file0 date >path1/file1 @@ -75,7 +86,7 @@ EOF test_expect_success \ 'validate git ls-files -k output.' \ - 'diff .output .expected' + 'test_cmp .expected .output' test_expect_success \ 'git ls-files -m to show modified files.' \ @@ -91,6 +102,6 @@ EOF test_expect_success \ 'validate git ls-files -m output.' \ - 'diff .output .expected' + 'test_cmp .expected .output' test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 0de613dc53..9b3fa2bdcd 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -276,6 +276,9 @@ test_expect_success 'fail if the index has unresolved entries' ' test_must_fail git merge "$c5" && test_must_fail git merge "$c5" 2> out && + grep "You have not concluded your merge" out && + rm -f .git/MERGE_HEAD && + test_must_fail git merge "$c5" 2> out && grep "You are in the middle of a conflicted merge" out ' diff --git a/t/t3031-merge-criscross.sh b/t/t3031-merge-criscross.sh new file mode 100755 index 0000000000..7f41607c56 --- /dev/null +++ b/t/t3031-merge-criscross.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +test_description='merge-recursive backend test' + +. ./test-lib.sh + +# A <- create some files +# / \ +# B C <- cause rename/delete conflicts between B and C +# / \ +# |\ /| +# | D E | +# | \ / | +# | X | +# | / \ | +# | / \ | +# |/ \| +# F G <- merge E into B, D into C +# \ / +# \ / +# \ / +# H <- recursive merge crashes +# + +# initialize +test_expect_success 'setup repo with criss-cross history' ' + mkdir data && + + # create a bunch of files + n=1 && + while test $n -le 10 + do + echo $n > data/$n && + n=$(($n+1)) || + break + done && + + # check them in + git add data && + git commit -m A && + git branch A && + + # a file in one branch + git checkout -b B A && + git rm data/9 && + git add data && + git commit -m B && + + # with a branch off of it + git branch D && + + # put some commits on D + git checkout D && + echo testD > data/testD && + git add data && + git commit -m D && + + # back up to the top, create another branch and cause + # a rename conflict with the file we deleted earlier + git checkout -b C A && + git mv data/9 data/new-9 && + git add data && + git commit -m C && + + # with a branch off of it + git branch E && + + # put a commit on E + git checkout E && + echo testE > data/testE && + git add data && + git commit -m E && + + # now, merge E into B + git checkout B && + test_must_fail git merge E && + # force-resolve + git add data && + git commit -m F && + git branch F && + + # and merge D into C + git checkout C && + test_must_fail git merge D && + # force-resolve + git add data && + git commit -m G && + git branch G +' + +test_expect_success 'recursive merge between F and G, causes segfault' ' + git merge F +' + +test_done diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh index 6e6a2542a2..ee60d03fe8 100755 --- a/t/t3100-ls-tree-restrict.sh +++ b/t/t3100-ls-tree-restrict.sh @@ -22,9 +22,21 @@ test_expect_success \ 'setup' \ 'mkdir path2 path2/baz && echo Hi >path0 && - ln -s path0 path1 && + if test_have_prereq SYMLINKS + then + ln -s path0 path1 && + ln -s ../path1 path2/bazbo + make_expected () { + cat >expected + } + else + printf path0 > path1 && + printf ../path1 > path2/bazbo + make_expected () { + sed -e "s/120000 /100644 /" >expected + } + fi && echo Lo >path2/foo && - ln -s ../path1 path2/bazbo && echo Mi >path2/baz/b && find path? \( -type f -o -type l \) -print | xargs git update-index --add && @@ -41,7 +53,7 @@ test_output () { test_expect_success \ 'ls-tree plain' \ 'git ls-tree $tree >current && - cat >expected <<\EOF && + make_expected <<\EOF && 100644 blob X path0 120000 blob X path1 040000 tree X path2 @@ -51,7 +63,7 @@ EOF test_expect_success \ 'ls-tree recursive' \ 'git ls-tree -r $tree >current && - cat >expected <<\EOF && + make_expected <<\EOF && 100644 blob X path0 120000 blob X path1 100644 blob X path2/baz/b @@ -63,7 +75,7 @@ EOF test_expect_success \ 'ls-tree recursive with -t' \ 'git ls-tree -r -t $tree >current && - cat >expected <<\EOF && + make_expected <<\EOF && 100644 blob X path0 120000 blob X path1 040000 tree X path2 @@ -77,7 +89,7 @@ EOF test_expect_success \ 'ls-tree recursive with -d' \ 'git ls-tree -r -d $tree >current && - cat >expected <<\EOF && + make_expected <<\EOF && 040000 tree X path2 040000 tree X path2/baz EOF @@ -86,7 +98,7 @@ EOF test_expect_success \ 'ls-tree filtered with path' \ 'git ls-tree $tree path >current && - cat >expected <<\EOF && + make_expected <<\EOF && EOF test_output' @@ -96,7 +108,7 @@ EOF test_expect_success \ 'ls-tree filtered with path1 path0' \ 'git ls-tree $tree path1 path0 >current && - cat >expected <<\EOF && + make_expected <<\EOF && 100644 blob X path0 120000 blob X path1 EOF @@ -105,7 +117,7 @@ EOF test_expect_success \ 'ls-tree filtered with path0/' \ 'git ls-tree $tree path0/ >current && - cat >expected <<\EOF && + make_expected <<\EOF && EOF test_output' @@ -114,7 +126,7 @@ EOF test_expect_success \ 'ls-tree filtered with path2' \ 'git ls-tree $tree path2 >current && - cat >expected <<\EOF && + make_expected <<\EOF && 040000 tree X path2 EOF test_output' @@ -123,7 +135,7 @@ EOF test_expect_success \ 'ls-tree filtered with path2/' \ 'git ls-tree $tree path2/ >current && - cat >expected <<\EOF && + make_expected <<\EOF && 040000 tree X path2/baz 120000 blob X path2/bazbo 100644 blob X path2/foo @@ -135,7 +147,7 @@ EOF test_expect_success \ 'ls-tree filtered with path2/baz' \ 'git ls-tree $tree path2/baz >current && - cat >expected <<\EOF && + make_expected <<\EOF && 040000 tree X path2/baz EOF test_output' @@ -143,14 +155,14 @@ EOF test_expect_success \ 'ls-tree filtered with path2/bak' \ 'git ls-tree $tree path2/bak >current && - cat >expected <<\EOF && + make_expected <<\EOF && EOF test_output' test_expect_success \ 'ls-tree -t filtered with path2/bak' \ 'git ls-tree -t $tree path2/bak >current && - cat >expected <<\EOF && + make_expected <<\EOF && 040000 tree X path2 EOF test_output' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 1b1e9ece57..d59a9b4aef 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -121,7 +121,7 @@ test_expect_success 'renaming a symref is not allowed' \ ! test -f .git/refs/heads/master3 ' -test_expect_success \ +test_expect_success SYMLINKS \ 'git branch -m u v should fail when the reflog for u is a symlink' ' git branch -l u && mv .git/logs/refs/heads/u real-u && diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh new file mode 100755 index 0000000000..809d1c4ed4 --- /dev/null +++ b/t/t3203-branch-output.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git branch display tests' +. ./test-lib.sh + +test_expect_success 'make commits' ' + echo content >file && + git add file && + git commit -m one && + echo content >>file && + git commit -a -m two +' + +test_expect_success 'make branches' ' + git branch branch-one + git branch branch-two HEAD^ +' + +test_expect_success 'make remote branches' ' + git update-ref refs/remotes/origin/branch-one branch-one + git update-ref refs/remotes/origin/branch-two branch-two + git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/branch-one +' + +cat >expect <<'EOF' + branch-one + branch-two +* master +EOF +test_expect_success 'git branch shows local branches' ' + git branch >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' + origin/HEAD -> origin/branch-one + origin/branch-one + origin/branch-two +EOF +test_expect_success 'git branch -r shows remote branches' ' + git branch -r >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' + branch-one + branch-two +* master + remotes/origin/HEAD -> origin/branch-one + remotes/origin/branch-one + remotes/origin/branch-two +EOF +test_expect_success 'git branch -a shows local and remote branches' ' + git branch -a >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +two +one +two +EOF +test_expect_success 'git branch -v shows branch summaries' ' + git branch -v >tmp && + awk "{print \$NF}" <tmp >actual && + test_cmp expect actual +' + +cat >expect <<'EOF' +* (no branch) + branch-one + branch-two + master +EOF +test_expect_success 'git branch shows detached HEAD properly' ' + git checkout HEAD^0 && + git branch >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index be7ae5a004..7f62bfb9dd 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -41,9 +41,40 @@ test_expect_success \ git tag topic ' +test_expect_success 'rebase on dirty worktree' ' + echo dirty >> A && + test_must_fail git rebase master' + +test_expect_success 'rebase on dirty cache' ' + git add A && + test_must_fail git rebase master' + test_expect_success 'rebase against master' ' + git reset --hard HEAD && git rebase master' +test_expect_success 'rebase against master twice' ' + git rebase master 2>err && + grep "Current branch my-topic-branch is up to date" err +' + +test_expect_success 'rebase against master twice with --force' ' + git rebase --force-rebase master >out && + grep "Current branch my-topic-branch is up to date, rebase forced" out +' + +test_expect_success 'rebase against master twice from another branch' ' + git checkout my-topic-branch^ && + git rebase master my-topic-branch 2>err && + grep "Current branch my-topic-branch is up to date" err +' + +test_expect_success 'rebase fast-forward to master' ' + git checkout my-topic-branch^ && + git rebase my-topic-branch 2>err && + grep "Fast-forwarded HEAD to my-topic-branch" err +' + test_expect_success \ 'the rebase operation should not have destroyed author information' \ '! (git log | grep "Author:" | grep "<>")' @@ -83,9 +114,9 @@ test_expect_success 'rebase a single mode change' ' git checkout -b modechange HEAD^ && echo 1 > X && git add X && - chmod a+x A && + test_chmod +x A && test_tick && - git commit -m modechange A X && + git commit -m modechange && GIT_TRACE=1 git rebase master ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 603b003edf..c32ff6682b 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -459,4 +459,15 @@ test_expect_success 'submodule rebase -i' ' FAKE_LINES="1 squash 2 3" git rebase -i A ' +test_expect_success 'avoid unnecessary reset' ' + git checkout master && + test-chmtime =123456789 file3 && + git update-index --refresh && + HEAD=$(git rev-parse HEAD) && + git rebase -i HEAD~4 && + test $HEAD = $(git rev-parse HEAD) && + MTIME=$(test-chmtime -v +0 file3 | sed 's/[^0-9].*$//') && + test 123456789 = $MTIME +' + test_done diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index 5391080943..85fc7c4af8 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -22,7 +22,8 @@ test_expect_success setup ' git checkout topic && quick_one A && quick_one B && - quick_one Z + quick_one Z && + git tag start ' @@ -41,4 +42,24 @@ test_expect_success 'rebase -m' ' ' +test_expect_success 'rebase --stat' ' + git reset --hard start + git rebase --stat master >diffstat.txt && + grep "^ fileX | *1 +$" diffstat.txt +' + +test_expect_success 'rebase w/config rebase.stat' ' + git reset --hard start + git config rebase.stat true && + git rebase master >diffstat.txt && + grep "^ fileX | *1 +$" diffstat.txt +' + +test_expect_success 'rebase -n overrides config rebase.stat config' ' + git reset --hard start + git config rebase.stat true && + git rebase -n master >diffstat.txt && + ! grep "^ fileX | *1 +$" diffstat.txt +' + test_done diff --git a/t/t3409-rebase-hook.sh b/t/t3413-rebase-hook.sh index 098b75507b..098b75507b 100755 --- a/t/t3409-rebase-hook.sh +++ b/t/t3413-rebase-hook.sh diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index 95542e9cfe..76b1bb4545 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -12,30 +12,37 @@ test_expect_success \ 'Initialize test directory' \ "touch -- foo bar baz 'space embedded' -q && git add -- foo bar baz 'space embedded' -q && - git commit -m 'add normal files' && - test_tabs=y && - if touch -- 'tab embedded' 'newline -embedded' - then + git commit -m 'add normal files'" + +if touch -- 'tab embedded' 'newline +embedded' 2>/dev/null +then + test_set_prereq FUNNYNAMES +else + say 'Your filesystem does not allow tabs in filenames.' +fi + +test_expect_success FUNNYNAMES 'add files with funny names' " git add -- 'tab embedded' 'newline embedded' && git commit -m 'add files with tabs and newlines' - else - say 'Your filesystem does not allow tabs in filenames.' - test_tabs=n - fi" +" +# Determine rm behavior # Later we will try removing an unremovable path to make sure # git rm barfs, but if the test is run as root that cannot be # arranged. -test_expect_success \ - 'Determine rm behavior' \ - ': >test-file - chmod a-w . - rm -f test-file - test -f test-file && test_failed_remove=y - chmod 775 . - rm -f test-file' +: >test-file +chmod a-w . +rm -f test-file 2>/dev/null +if test -f test-file +then + test_set_prereq RO_DIR +else + say 'skipping removal failure test (perhaps running as root?)' +fi +chmod 775 . +rm -f test-file test_expect_success \ 'Pre-check that foo exists and is in index before git rm foo' \ @@ -100,20 +107,16 @@ test_expect_success \ 'Test that "git rm -- -q" succeeds (remove a file that looks like an option)' \ 'git rm -- -q' -test "$test_tabs" = y && test_expect_success \ +test_expect_success FUNNYNAMES \ "Test that \"git rm -f\" succeeds with embedded space, tab, or newline characters." \ "git rm -f 'space embedded' 'tab embedded' 'newline embedded'" -if test "$test_failed_remove" = y; then -chmod a-w . -test_expect_success \ - 'Test that "git rm -f" fails if its rm fails' \ - 'test_must_fail git rm -f baz' -chmod 775 . -else - test_expect_success 'skipping removal failure (perhaps running as root?)' : -fi +test_expect_success RO_DIR 'Test that "git rm -f" fails if its rm fails' ' + chmod a-w . && + test_must_fail git rm -f baz && + chmod 775 . +' test_expect_success \ 'When the rm in "git rm -f" fails, it should not remove the file from the index' \ diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 9f6454d2ff..6ae5a2cd95 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -30,7 +30,7 @@ test_expect_success \ *) echo fail; git ls-files --stage xfoo1; (exit 1);; esac' -test_expect_success 'git add: filemode=0 should not get confused by symlink' ' +test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' ' rm -f xfoo1 && ln -s foo xfoo1 && git add xfoo1 && @@ -51,7 +51,7 @@ test_expect_success \ *) echo fail; git ls-files --stage xfoo2; (exit 1);; esac' -test_expect_success 'git add: filemode=0 should not get confused by symlink' ' +test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' ' rm -f xfoo2 && ln -s foo xfoo2 && git update-index --add xfoo2 && @@ -61,7 +61,7 @@ test_expect_success 'git add: filemode=0 should not get confused by symlink' ' esac ' -test_expect_success \ +test_expect_success SYMLINKS \ 'git update-index --add: Test that executable bit is not used...' \ 'git config core.filemode 0 && ln -s xfoo2 xfoo3 && @@ -179,7 +179,7 @@ test_expect_success 'git add --refresh' ' test -z "`git diff-index HEAD -- foo`" ' -test_expect_success 'git add should fail atomically upon an unreadable file' ' +test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable file' ' git reset --hard && date >foo1 && date >foo2 && @@ -190,7 +190,7 @@ test_expect_success 'git add should fail atomically upon an unreadable file' ' rm -f foo2 -test_expect_success 'git add --ignore-errors' ' +test_expect_success POSIXPERM 'git add --ignore-errors' ' git reset --hard && date >foo1 && date >foo2 && @@ -201,7 +201,7 @@ test_expect_success 'git add --ignore-errors' ' rm -f foo2 -test_expect_success 'git add (add.ignore-errors)' ' +test_expect_success POSIXPERM 'git add (add.ignore-errors)' ' git config add.ignore-errors 1 && git reset --hard && date >foo1 && @@ -212,7 +212,7 @@ test_expect_success 'git add (add.ignore-errors)' ' ' rm -f foo2 -test_expect_success 'git add (add.ignore-errors = false)' ' +test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' ' git config add.ignore-errors 0 && git reset --hard && date >foo1 && @@ -221,8 +221,21 @@ test_expect_success 'git add (add.ignore-errors = false)' ' test_must_fail git add --verbose . && ! ( git ls-files foo1 | grep foo1 ) ' +rm -f foo2 + +test_expect_success '--no-ignore-errors overrides config' ' + git config add.ignore-errors 1 && + git reset --hard && + date >foo1 && + date >foo2 && + chmod 0 foo2 && + test_must_fail git add --verbose --no-ignore-errors . && + ! ( git ls-files foo1 | grep foo1 ) && + git config add.ignore-errors 0 +' +rm -f foo2 -test_expect_success 'git add '\''fo\[ou\]bar'\'' ignores foobar' ' +test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" ' git reset --hard && touch fo\[ou\]bar foobar && git add '\''fo\[ou\]bar'\'' && @@ -230,4 +243,16 @@ test_expect_success 'git add '\''fo\[ou\]bar'\'' ignores foobar' ' ! ( git ls-files foobar | grep foobar ) ' +test_expect_success 'git add to resolve conflicts on otherwise ignored path' ' + git reset --hard && + H=$(git rev-parse :1/2/a) && + ( + echo "100644 $H 1 track-this" + echo "100644 $H 3 track-this" + ) | git update-index --index-info && + echo track-this >>.gitignore && + echo resolved >track-this && + git add track-this +' + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index e95663d8e6..fd2a55a5c2 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -3,6 +3,11 @@ test_description='add -i basic tests' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git add -i tests, perl not available' + test_done +fi + test_expect_success 'setup (initial)' ' echo content >file && git add file && @@ -135,10 +140,12 @@ test_expect_success 'real edit works' ' if test "$(git config --bool core.filemode)" = false then - say 'skipping filemode tests (filesystem does not properly support modes)' + say 'skipping filemode tests (filesystem does not properly support modes)' else + test_set_prereq FILEMODE +fi -test_expect_success 'patch does not affect mode' ' +test_expect_success FILEMODE 'patch does not affect mode' ' git reset --hard && echo content >>file && chmod +x file && @@ -147,7 +154,7 @@ test_expect_success 'patch does not affect mode' ' git diff file | grep "new mode" ' -test_expect_success 'stage mode but not hunk' ' +test_expect_success FILEMODE 'stage mode but not hunk' ' git reset --hard && echo content >>file && chmod +x file && @@ -156,7 +163,44 @@ test_expect_success 'stage mode but not hunk' ' git diff file | grep "+content" ' -fi # end of tests disabled when filemode is not usable +test_expect_success 'setup again' ' + git reset --hard && + test_chmod +x file && + echo content >>file +' + +# Write the patch file with a new line at the top and bottom +cat >patch <<EOF +index 180b47c..b6f2c08 100644 +--- a/file ++++ b/file +@@ -1,2 +1,4 @@ ++firstline + baseline + content ++lastline +EOF +# Expected output, similar to the patch but w/ diff at the top +cat >expected <<EOF +diff --git a/file b/file +index b6f2c08..61b9053 100755 +--- a/file ++++ b/file +@@ -1,2 +1,4 @@ ++firstline + baseline + content ++lastline +EOF +# Test splitting the first patch, then adding both +test_expect_success 'add first line works' ' + git commit -am "clear local changes" && + git apply patch && + (echo s; echo y; echo y) | git add -p file && + git diff --cached > diff && + test_cmp expected diff +' + test_done diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh new file mode 100755 index 0000000000..4ee47cc9a8 --- /dev/null +++ b/t/t3702-add-edit.sh @@ -0,0 +1,121 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes E. Schindelin +# + +test_description='add -e basic tests' +. ./test-lib.sh + + +cat > file << EOF +LO, praise of the prowess of people-kings +of spear-armed Danes, in days long sped, +we have heard, and what honor the athelings won! +Oft Scyld the Scefing from squadroned foes, +from many a tribe, the mead-bench tore, +awing the earls. Since erst he lay +friendless, a foundling, fate repaid him: +for he waxed under welkin, in wealth he throve, +till before him the folk, both far and near, +who house by the whale-path, heard his mandate, +gave him gifts: a good king he! +EOF + +cat > second-part << EOF +To him an heir was afterward born, +a son in his halls, whom heaven sent +to favor the folk, feeling their woe +that erst they had lacked an earl for leader +so long a while; the Lord endowed him, +the Wielder of Wonder, with world's renown. +EOF + +test_expect_success 'setup' ' + + git add file && + test_tick && + git commit -m initial file + +' + +cat > expected-patch << EOF +diff --git a/file b/file +index b9834b5..9020acb 100644 +--- a/file ++++ b/file +@@ -1,11 +1,6 @@ +-LO, praise of the prowess of people-kings +-of spear-armed Danes, in days long sped, +-we have heard, and what honor the athelings won! +-Oft Scyld the Scefing from squadroned foes, +-from many a tribe, the mead-bench tore, +-awing the earls. Since erst he lay +-friendless, a foundling, fate repaid him: +-for he waxed under welkin, in wealth he throve, +-till before him the folk, both far and near, +-who house by the whale-path, heard his mandate, +-gave him gifts: a good king he! ++To him an heir was afterward born, ++a son in his halls, whom heaven sent ++to favor the folk, feeling their woe ++that erst they had lacked an earl for leader ++so long a while; the Lord endowed him, ++the Wielder of Wonder, with world's renown. +EOF + +cat > patch << EOF +diff --git a/file b/file +index b9834b5..ef6e94c 100644 +--- a/file ++++ b/file +@@ -3,1 +3,333 @@ of spear-armed Danes, in days long sped, + we have heard, and what honor the athelings won! ++ + Oft Scyld the Scefing from squadroned foes, +@@ -2,7 +1,5 @@ awing the earls. Since erst he lay + friendless, a foundling, fate repaid him: ++ + for he waxed under welkin, in wealth he throve, +EOF + +cat > expected << EOF +diff --git a/file b/file +index b9834b5..ef6e94c 100644 +--- a/file ++++ b/file +@@ -1,10 +1,12 @@ + LO, praise of the prowess of people-kings + of spear-armed Danes, in days long sped, + we have heard, and what honor the athelings won! ++ + Oft Scyld the Scefing from squadroned foes, + from many a tribe, the mead-bench tore, + awing the earls. Since erst he lay + friendless, a foundling, fate repaid him: ++ + for he waxed under welkin, in wealth he throve, + till before him the folk, both far and near, + who house by the whale-path, heard his mandate, +EOF + +echo "#!$SHELL_PATH" >fake-editor.sh +cat >> fake-editor.sh <<\EOF +mv -f "$1" orig-patch && +mv -f patch "$1" +EOF + +test_set_editor "$(pwd)/fake-editor.sh" +chmod a+x fake-editor.sh + +test_expect_success 'add -e' ' + + cp second-part file && + git add -e && + test_cmp second-part file && + test_cmp orig-patch expected-patch && + git diff --cached > out && + test_cmp out expected + +' + +test_done diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh index 784c31aec9..256c4c9701 100755 --- a/t/t3900-i18n-commit.sh +++ b/t/t3900-i18n-commit.sh @@ -9,7 +9,15 @@ test_description='commit and log output encodings' compare_with () { git show -s $1 | sed -e '1,/^$/d' -e 's/^ //' >current && - test_cmp current "$2" + case "$3" in + '') + test_cmp "$2" current ;; + ?*) + iconv -f "$3" -t UTF-8 >current.utf8 <current && + iconv -f "$3" -t UTF-8 >expect.utf8 <"$2" && + test_cmp expect.utf8 current.utf8 + ;; + esac } test_expect_success setup ' @@ -26,7 +34,7 @@ test_expect_success 'no encoding header for base case' ' test z = "z$E" ' -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "$H setup" ' git config i18n.commitencoding $H && @@ -36,7 +44,7 @@ do ' done -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "check encoding header for $H" ' E=$(git cat-file commit '$H' | sed -ne "s/^encoding //p") && @@ -53,14 +61,14 @@ test_expect_success 'config to remove customization' ' else test z = "z$Z" fi && - git config i18n.commitencoding utf-8 + git config i18n.commitencoding UTF-8 ' -test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' ' - compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt +test_expect_success 'ISO8859-1 should be shown in UTF-8 now' ' + compare_with ISO8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt ' -for H in EUCJP ISO-2022-JP +for H in eucJP ISO-2022-JP do test_expect_success "$H should be shown in UTF-8 now" ' compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt @@ -78,7 +86,7 @@ test_expect_success 'config to add customization' ' fi ' -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "$H should be shown in itself now" ' git config i18n.commitencoding '$H' && @@ -87,32 +95,38 @@ do done test_expect_success 'config to tweak customization' ' - git config i18n.logoutputencoding utf-8 + git config i18n.logoutputencoding UTF-8 ' -test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' ' - compare_with ISO-8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt +test_expect_success 'ISO8859-1 should be shown in UTF-8 now' ' + compare_with ISO8859-1 "$TEST_DIRECTORY"/t3900/1-UTF-8.txt ' -for H in EUCJP ISO-2022-JP +for H in eucJP ISO-2022-JP do test_expect_success "$H should be shown in UTF-8 now" ' compare_with '$H' "$TEST_DIRECTORY"/t3900/2-UTF-8.txt ' done -for J in EUCJP ISO-2022-JP +for J in eucJP ISO-2022-JP do + if test "$J" = ISO-2022-JP + then + ICONV=$J + else + ICONV= + fi git config i18n.logoutputencoding $J - for H in EUCJP ISO-2022-JP + for H in eucJP ISO-2022-JP do test_expect_success "$H should be shown in $J now" ' - compare_with '$H' "$TEST_DIRECTORY"/t3900/'$J'.txt + compare_with '$H' "$TEST_DIRECTORY"/t3900/'$J'.txt $ICONV ' done done -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "No conversion with $H" ' compare_with "--encoding=none '$H'" "$TEST_DIRECTORY"/t3900/'$H'.txt diff --git a/t/t3900/ISO-8859-1.txt b/t/t3900/ISO8859-1.txt index 7cbef0ee6f..7cbef0ee6f 100644 --- a/t/t3900/ISO-8859-1.txt +++ b/t/t3900/ISO8859-1.txt diff --git a/t/t3900/EUCJP.txt b/t/t3900/eucJP.txt index 546f2aac01..546f2aac01 100644 --- a/t/t3900/EUCJP.txt +++ b/t/t3900/eucJP.txt diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh index 7655da3f8d..31a5770b34 100755 --- a/t/t3901-i18n-patch.sh +++ b/t/t3901-i18n-patch.sh @@ -17,9 +17,9 @@ check_encoding () { git cat-file commit HEAD~$j | case "$header" in 8859) - grep "^encoding ISO-8859-1" ;; + grep "^encoding ISO8859-1" ;; *) - ! grep "^encoding ISO-8859-1" ;; + grep "^encoding ISO8859-1"; test "$?" != 0 ;; esac || { bad=1 break @@ -55,7 +55,7 @@ test_expect_success setup ' git commit -s -m "Second on side" && # the second one on the side branch is ISO-8859-1 - git config i18n.commitencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && # use author and committer name in ISO-8859-1 to match it. . "$TEST_DIRECTORY"/t3901-8859-1.txt && test_tick && @@ -68,14 +68,14 @@ test_expect_success setup ' ' test_expect_success 'format-patch output (ISO-8859-1)' ' - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.logoutputencoding ISO8859-1 && git format-patch --stdout master..HEAD^ >out-l1 && git format-patch --stdout HEAD^ >out-l2 && - grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l1 && - grep "^From: =?ISO-8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l1 && - grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l2 && - grep "^From: =?ISO-8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l2 + grep "^Content-Type: text/plain; charset=ISO8859-1" out-l1 && + grep "^From: =?ISO8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l1 && + grep "^Content-Type: text/plain; charset=ISO8859-1" out-l2 && + grep "^From: =?ISO8859-1?q?=C1=E9=ED=20=F3=FA?=" out-l2 ' test_expect_success 'format-patch output (UTF-8)' ' @@ -110,7 +110,7 @@ test_expect_success 'rebase (U/U)' ' test_expect_success 'rebase (U/L)' ' git config i18n.commitencoding UTF-8 && - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.logoutputencoding ISO8859-1 && . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard side && @@ -121,8 +121,8 @@ test_expect_success 'rebase (U/L)' ' test_expect_success 'rebase (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result - git config i18n.commitencoding ISO-8859-1 && - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && + git config i18n.logoutputencoding ISO8859-1 && . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard side && @@ -134,7 +134,7 @@ test_expect_success 'rebase (L/L)' ' test_expect_success 'rebase (L/U)' ' # This is pathological -- use UTF-8 as intermediate form # to get ISO-8859-1 results. - git config i18n.commitencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && . "$TEST_DIRECTORY"/t3901-8859-1.txt && @@ -162,8 +162,8 @@ test_expect_success 'cherry-pick(U/U)' ' test_expect_success 'cherry-pick(L/L)' ' # Both the commitencoding and logoutputencoding is set to ISO-8859-1 - git config i18n.commitencoding ISO-8859-1 && - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && + git config i18n.logoutputencoding ISO8859-1 && . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard master && @@ -178,7 +178,7 @@ test_expect_success 'cherry-pick(U/L)' ' # Commitencoding is set to UTF-8 but logoutputencoding is ISO-8859-1 git config i18n.commitencoding UTF-8 && - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.logoutputencoding ISO8859-1 && . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard master && @@ -193,7 +193,7 @@ test_expect_success 'cherry-pick(L/U)' ' # Again, the commitencoding is set to ISO-8859-1 but # logoutputencoding is set to UTF-8. - git config i18n.commitencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && . "$TEST_DIRECTORY"/t3901-8859-1.txt && @@ -218,7 +218,7 @@ test_expect_success 'rebase --merge (U/U)' ' test_expect_success 'rebase --merge (U/L)' ' git config i18n.commitencoding UTF-8 && - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.logoutputencoding ISO8859-1 && . "$TEST_DIRECTORY"/t3901-utf8.txt && git reset --hard side && @@ -229,8 +229,8 @@ test_expect_success 'rebase --merge (U/L)' ' test_expect_success 'rebase --merge (L/L)' ' # In this test we want ISO-8859-1 encoded commits as the result - git config i18n.commitencoding ISO-8859-1 && - git config i18n.logoutputencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && + git config i18n.logoutputencoding ISO8859-1 && . "$TEST_DIRECTORY"/t3901-8859-1.txt && git reset --hard side && @@ -242,7 +242,7 @@ test_expect_success 'rebase --merge (L/L)' ' test_expect_success 'rebase --merge (L/U)' ' # This is pathological -- use UTF-8 as intermediate form # to get ISO-8859-1 results. - git config i18n.commitencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && git config i18n.logoutputencoding UTF-8 && . "$TEST_DIRECTORY"/t3901-8859-1.txt && diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index cc3681f161..18695ce821 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -258,4 +258,12 @@ test_expect_success \ git diff-tree -r -R $tree_A $tree_B >.test-b && cmp -s .test-a .test-b' +test_expect_success \ + 'diff can read from stdin' \ + 'test_must_fail git diff --no-index -- MN - < NN | + grep -v "^index" | sed "s#/-#/NN#" >.test-a && + test_must_fail git diff --no-index -- MN NN | + grep -v "^index" >.test-b && + test_cmp .test-a .test-b' + test_done diff --git a/t/t4004-diff-rename-symlink.sh b/t/t4004-diff-rename-symlink.sh index b35af9b42d..a4da1196a9 100755 --- a/t/t4004-diff-rename-symlink.sh +++ b/t/t4004-diff-rename-symlink.sh @@ -12,6 +12,12 @@ by an edit for them. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + test_expect_success \ 'prepare reference tree' \ 'echo xyzzy | tr -d '\\\\'012 >yomin && diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh index 4e92fce1d0..8c1b81e248 100755 --- a/t/t4006-diff-mode.sh +++ b/t/t4006-diff-mode.sh @@ -15,21 +15,10 @@ test_expect_success \ tree=`git write-tree` && echo $tree' -if [ "$(git config --get core.filemode)" = false ] -then - say 'filemode disabled on the filesystem, using update-index --chmod=+x' - test_expect_success \ - 'git update-index --chmod=+x' \ - 'git update-index rezrov && - git update-index --chmod=+x rezrov && - git diff-index $tree >current' -else - test_expect_success \ - 'chmod' \ - 'chmod +x rezrov && - git update-index rezrov && - git diff-index $tree >current' -fi +test_expect_success \ + 'chmod' \ + 'test_chmod +x rezrov && + git diff-index $tree >current' _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" diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 7e343a9cd1..e19ca65885 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -99,7 +99,7 @@ test_expect_success \ 'validate result of -B -M (#4)' \ 'compare_diff_raw expected current' -test_expect_success \ +test_expect_success SYMLINKS \ 'make file0 into something completely different' \ 'rm -f file0 && ln -s frotz file0 && @@ -114,7 +114,7 @@ cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100 file1 EOF -test_expect_success \ +test_expect_success SYMLINKS \ 'validate result of -B (#5)' \ 'compare_diff_raw expected current' @@ -129,7 +129,7 @@ cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R file0 file1 EOF -test_expect_success \ +test_expect_success SYMLINKS \ 'validate result of -B -M (#6)' \ 'compare_diff_raw expected current' @@ -144,7 +144,7 @@ cat >expected <<\EOF :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M file1 EOF -test_expect_success \ +test_expect_success SYMLINKS \ 'validate result of -M (#7)' \ 'compare_diff_raw expected current' diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index 9055c8b318..d7e327cc5b 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -9,6 +9,12 @@ test_description='Test diff of symlinks. . ./test-lib.sh . "$TEST_DIRECTORY"/diff-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + cat > expected << EOF diff --git a/frotz b/frotz new file mode 120000 diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index 3cf5b5c4ea..f64aa48d24 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -87,7 +87,7 @@ nul_to_q() { test_expect_success 'diff --no-index with binary creation' ' echo Q | q_to_nul >binary && - (:# hide error code from diff, which just indicates differences + (: hide error code from diff, which just indicates differences git diff --binary --no-index /dev/null binary >current || true ) && diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 9c709022ef..8b33321f8c 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -101,8 +101,7 @@ do '' | '#'*) continue ;; esac test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'` - cnt=`expr $test_count + 1` - pfx=`printf "%04d" $cnt` + pfx=`printf "%04d" $test_count` expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" @@ -207,6 +206,10 @@ log --root -c --patch-with-stat --summary master log --root --cc --patch-with-stat --summary master log -SF master log -SF -p master +log --decorate --all + +rev-list --parents HEAD +rev-list --children HEAD whatchanged master whatchanged -p master @@ -243,11 +246,12 @@ format-patch --stdout initial..master format-patch --stdout --no-numbered initial..master format-patch --stdout --numbered initial..master format-patch --attach --stdout initial..side +format-patch --attach --stdout --suffix=.diff initial..side format-patch --attach --stdout initial..master^ format-patch --attach --stdout initial..master format-patch --inline --stdout initial..side format-patch --inline --stdout initial..master^ -format-patch --inline --stdout initial..master +format-patch --inline --stdout --numbered-files initial..master format-patch --inline --stdout initial..master format-patch --inline --stdout --subject-prefix=TESTCASE initial..master config format.subjectprefix DIFFERENT_PREFIX @@ -268,6 +272,7 @@ diff --no-index --name-status dir2 dir diff --no-index --name-status -- dir2 dir diff --no-index dir dir3 diff master master^ side +diff --dirstat master~1 master~2 EOF test_done diff --git a/t/t4013/diff.diff_--dirstat_master~1_master~2 b/t/t4013/diff.diff_--dirstat_master~1_master~2 new file mode 100644 index 0000000000..b672e1ca63 --- /dev/null +++ b/t/t4013/diff.diff_--dirstat_master~1_master~2 @@ -0,0 +1,3 @@ +$ git diff --dirstat master~1 master~2 + 40.0% dir/ +$ diff --git a/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side new file mode 100644 index 0000000000..52116d3ead --- /dev/null +++ b/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side @@ -0,0 +1,61 @@ +$ git format-patch --attach --stdout --suffix=.diff initial..side +From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:03:00 +0000 +Subject: [PATCH] Side +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + +--- + dir/sub | 2 ++ + file0 | 3 +++ + file3 | 4 ++++ + 3 files changed, 9 insertions(+), 0 deletions(-) + create mode 100644 file3 + + +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="0001-Side.diff" +Content-Transfer-Encoding: 8bit +Content-Disposition: attachment; filename="0001-Side.diff" + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +--------------g-i-t--v-e-r-s-i-o-n-- + + +$ diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master b/t/t4013/diff.format-patch_--attach_--stdout_initial..master index e5ab74437e..ce49bd676e 100644 --- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master +++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master @@ -22,9 +22,9 @@ This is the second commit. --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Type: text/x-patch; name="0001-Second.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Disposition: attachment; filename="0001-Second.patch" diff --git a/dir/sub b/dir/sub index 35d242b..8422d40 100644 @@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Type: text/x-patch; name="0002-Third.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Disposition: attachment; filename="0002-Third.patch" diff --git a/dir/sub b/dir/sub index 8422d40..cead32e 100644 @@ -129,9 +129,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Type: text/x-patch; name="0003-Side.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Disposition: attachment; filename="0003-Side.patch" diff --git a/dir/sub b/dir/sub index 35d242b..7289e35 100644 diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ index 2c71d20d37..5f1b23863b 100644 --- a/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ +++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..master^ @@ -22,9 +22,9 @@ This is the second commit. --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Type: text/x-patch; name="0001-Second.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Disposition: attachment; filename="0001-Second.patch" diff --git a/dir/sub b/dir/sub index 35d242b..8422d40 100644 @@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Type: text/x-patch; name="0002-Third.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Disposition: attachment; filename="0002-Third.patch" diff --git a/dir/sub b/dir/sub index 8422d40..cead32e 100644 diff --git a/t/t4013/diff.format-patch_--attach_--stdout_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_initial..side index 38f790290a..4a2364abc2 100644 --- a/t/t4013/diff.format-patch_--attach_--stdout_initial..side +++ b/t/t4013/diff.format-patch_--attach_--stdout_initial..side @@ -20,9 +20,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Type: text/x-patch; name="0001-Side.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: attachment; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Disposition: attachment; filename="0001-Side.patch" diff --git a/dir/sub b/dir/sub index 35d242b..7289e35 100644 diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master new file mode 100644 index 0000000000..43b81eba54 --- /dev/null +++ b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master @@ -0,0 +1,170 @@ +$ git format-patch --inline --stdout --numbered-files initial..master +From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:01:00 +0000 +Subject: [PATCH 1/3] Second +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + + +This is the second commit. +--- + dir/sub | 2 ++ + file0 | 3 +++ + file2 | 3 --- + 3 files changed, 5 insertions(+), 3 deletions(-) + delete mode 100644 file2 + + +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="1" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="1" + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +--------------g-i-t--v-e-r-s-i-o-n-- + + + +From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:02:00 +0000 +Subject: [PATCH 2/3] Third +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + +--- + dir/sub | 2 ++ + file1 | 3 +++ + 2 files changed, 5 insertions(+), 0 deletions(-) + create mode 100644 file1 + + +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="2" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="2" + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +--------------g-i-t--v-e-r-s-i-o-n-- + + + +From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001 +From: A U Thor <author@example.com> +Date: Mon, 26 Jun 2006 00:03:00 +0000 +Subject: [PATCH 3/3] Side +MIME-Version: 1.0 +Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n" + +This is a multi-part message in MIME format. +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/plain; charset=UTF-8; format=fixed +Content-Transfer-Encoding: 8bit + +--- + dir/sub | 2 ++ + file0 | 3 +++ + file3 | 4 ++++ + 3 files changed, 9 insertions(+), 0 deletions(-) + create mode 100644 file3 + + +--------------g-i-t--v-e-r-s-i-o-n +Content-Type: text/x-patch; name="3" +Content-Transfer-Encoding: 8bit +Content-Disposition: inline; filename="3" + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +--------------g-i-t--v-e-r-s-i-o-n-- + + +$ diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master index 58f8a7b7d6..ca3f60bf0e 100644 --- a/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master +++ b/t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master @@ -22,9 +22,9 @@ This is the second commit. --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Type: text/x-patch; name="0001-Second.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Disposition: inline; filename="0001-Second.patch" diff --git a/dir/sub b/dir/sub index 35d242b..8422d40 100644 @@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Type: text/x-patch; name="0002-Third.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Disposition: inline; filename="0002-Third.patch" diff --git a/dir/sub b/dir/sub index 8422d40..cead32e 100644 @@ -129,9 +129,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Type: text/x-patch; name="0003-Side.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Disposition: inline; filename="0003-Side.patch" diff --git a/dir/sub b/dir/sub index 35d242b..7289e35 100644 diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_initial..master index 9e7bbdffa2..08f23014bc 100644 --- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master +++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master @@ -22,9 +22,9 @@ This is the second commit. --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Type: text/x-patch; name="0001-Second.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Disposition: inline; filename="0001-Second.patch" diff --git a/dir/sub b/dir/sub index 35d242b..8422d40 100644 @@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Type: text/x-patch; name="0002-Third.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Disposition: inline; filename="0002-Third.patch" diff --git a/dir/sub b/dir/sub index 8422d40..cead32e 100644 @@ -129,9 +129,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Type: text/x-patch; name="0003-Side.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Disposition: inline; filename="0003-Side.patch" diff --git a/dir/sub b/dir/sub index 35d242b..7289e35 100644 diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ index f881f644cc..07f1230d31 100644 --- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ +++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^ @@ -22,9 +22,9 @@ This is the second commit. --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Type: text/x-patch; name="0001-Second.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Disposition: inline; filename="0001-Second.patch" diff --git a/dir/sub b/dir/sub index 35d242b..8422d40 100644 @@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Type: text/x-patch; name="0002-Third.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff" +Content-Disposition: inline; filename="0002-Third.patch" diff --git a/dir/sub b/dir/sub index 8422d40..cead32e 100644 diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ index 4f258b8858..29e00ab8af 100644 --- a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ +++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ @@ -22,9 +22,9 @@ This is the second commit. --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Type: text/x-patch; name="0001-Second.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff" +Content-Disposition: inline; filename="0001-Second.patch" diff --git a/dir/sub b/dir/sub index 35d242b..8422d40 100644 diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..side b/t/t4013/diff.format-patch_--inline_--stdout_initial..side index e86dce69a3..67633d424a 100644 --- a/t/t4013/diff.format-patch_--inline_--stdout_initial..side +++ b/t/t4013/diff.format-patch_--inline_--stdout_initial..side @@ -20,9 +20,9 @@ Content-Transfer-Encoding: 8bit --------------g-i-t--v-e-r-s-i-o-n -Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Type: text/x-patch; name="0001-Side.patch" Content-Transfer-Encoding: 8bit -Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff" +Content-Disposition: inline; filename="0001-Side.patch" diff --git a/dir/sub b/dir/sub index 35d242b..7289e35 100644 diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all new file mode 100644 index 0000000000..954210ea90 --- /dev/null +++ b/t/t4013/diff.log_--decorate_--all @@ -0,0 +1,34 @@ +$ git log --decorate --all +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (master) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (side) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a (initial) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.rev-list_--children_HEAD b/t/t4013/diff.rev-list_--children_HEAD new file mode 100644 index 0000000000..e7f17d5aa0 --- /dev/null +++ b/t/t4013/diff.rev-list_--children_HEAD @@ -0,0 +1,7 @@ +$ git rev-list --children HEAD +59d314ad6f356dd08601a4cd5e530381da3e3c64 +c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a 59d314ad6f356dd08601a4cd5e530381da3e3c64 +9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 59d314ad6f356dd08601a4cd5e530381da3e3c64 +1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +444ac553ac7612cc88969031b02b3767fb8a353a 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +$ diff --git a/t/t4013/diff.rev-list_--parents_HEAD b/t/t4013/diff.rev-list_--parents_HEAD new file mode 100644 index 0000000000..65d2a80208 --- /dev/null +++ b/t/t4013/diff.rev-list_--parents_HEAD @@ -0,0 +1,7 @@ +$ git rev-list --parents HEAD +59d314ad6f356dd08601a4cd5e530381da3e3c64 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a 444ac553ac7612cc88969031b02b3767fb8a353a +9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 444ac553ac7612cc88969031b02b3767fb8a353a +444ac553ac7612cc88969031b02b3767fb8a353a +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index f045898fe3..922a8941ed 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -16,9 +16,7 @@ test_expect_success setup ' git checkout -b side && for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file && - chmod +x elif && - git update-index file elif && - git update-index --chmod=+x elif && + test_chmod +x elif && git commit -m "Side changes #1" && for i in D E F; do echo "$i"; done >>file && @@ -130,6 +128,21 @@ test_expect_success 'additional command line cc' ' grep "^ *S. E. Cipient <scipient@example.com>$" patch5 ' +test_expect_success 'command line headers' ' + + git config --unset-all format.headers && + git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch6 && + grep "^Cc: R. E. Cipient <rcipient@example.com>$" patch6 +' + +test_expect_success 'configuration headers and command line headers' ' + + git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" && + git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch7 && + grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch7 && + grep "^ *S. E. Cipient <scipient@example.com>$" patch7 +' + test_expect_success 'multiple files' ' rm -rf patches/ && @@ -138,56 +151,243 @@ test_expect_success 'multiple files' ' ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch ' -test_expect_success 'thread' ' +check_threading () { + expect="$1" && + shift && + (git format-patch --stdout "$@"; echo $? > status.out) | + # Prints everything between the Message-ID and In-Reply-To, + # and replaces all Message-ID-lookalikes by a sequence number + perl -ne ' + if (/^(message-id|references|in-reply-to)/i) { + $printing = 1; + } elsif (/^\S/) { + $printing = 0; + } + if ($printing) { + $h{$1}=$i++ if (/<([^>]+)>/ and !exists $h{$1}); + for $k (keys %h) {s/$k/$h{$k}/}; + print; + } + print "---\n" if /^From /i; + ' > actual && + test 0 = "$(cat status.out)" && + test_cmp "$expect" actual +} + +cat >> expect.no-threading <<EOF +--- +--- +--- +EOF - rm -rf patches/ && +test_expect_success 'no threading' ' git checkout side && - git format-patch --thread -o patches/ master && - FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") && - for i in patches/0002-* patches/0003-* - do - grep "References: $FIRST_MID" $i && - grep "In-Reply-To: $FIRST_MID" $i || break - done + check_threading expect.no-threading master ' -test_expect_success 'thread in-reply-to' ' +cat > expect.thread <<EOF +--- +Message-Id: <0> +--- +Message-Id: <1> +In-Reply-To: <0> +References: <0> +--- +Message-Id: <2> +In-Reply-To: <0> +References: <0> +EOF - rm -rf patches/ && - git checkout side && - git format-patch --in-reply-to="<test.message>" --thread -o patches/ master && - FIRST_MID="<test.message>" && - for i in patches/* - do - grep "References: $FIRST_MID" $i && - grep "In-Reply-To: $FIRST_MID" $i || break - done +test_expect_success 'thread' ' + check_threading expect.thread --thread master ' -test_expect_success 'thread cover-letter' ' +cat > expect.in-reply-to <<EOF +--- +Message-Id: <0> +In-Reply-To: <1> +References: <1> +--- +Message-Id: <2> +In-Reply-To: <1> +References: <1> +--- +Message-Id: <3> +In-Reply-To: <1> +References: <1> +EOF - rm -rf patches/ && - git checkout side && - git format-patch --cover-letter --thread -o patches/ master && - FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") && - for i in patches/0001-* patches/0002-* patches/0003-* - do - grep "References: $FIRST_MID" $i && - grep "In-Reply-To: $FIRST_MID" $i || break - done +test_expect_success 'thread in-reply-to' ' + check_threading expect.in-reply-to --in-reply-to="<test.message>" \ + --thread master +' + +cat > expect.cover-letter <<EOF +--- +Message-Id: <0> +--- +Message-Id: <1> +In-Reply-To: <0> +References: <0> +--- +Message-Id: <2> +In-Reply-To: <0> +References: <0> +--- +Message-Id: <3> +In-Reply-To: <0> +References: <0> +EOF + +test_expect_success 'thread cover-letter' ' + check_threading expect.cover-letter --cover-letter --thread master ' +cat > expect.cl-irt <<EOF +--- +Message-Id: <0> +In-Reply-To: <1> +References: <1> +--- +Message-Id: <2> +In-Reply-To: <0> +References: <1> + <0> +--- +Message-Id: <3> +In-Reply-To: <0> +References: <1> + <0> +--- +Message-Id: <4> +In-Reply-To: <0> +References: <1> + <0> +EOF + test_expect_success 'thread cover-letter in-reply-to' ' + check_threading expect.cl-irt --cover-letter \ + --in-reply-to="<test.message>" --thread master +' - rm -rf patches/ && - git checkout side && - git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master && - FIRST_MID="<test.message>" && - for i in patches/* - do - grep "References: $FIRST_MID" $i && - grep "In-Reply-To: $FIRST_MID" $i || break - done +test_expect_success 'thread explicit shallow' ' + check_threading expect.cl-irt --cover-letter \ + --in-reply-to="<test.message>" --thread=shallow master +' + +cat > expect.deep <<EOF +--- +Message-Id: <0> +--- +Message-Id: <1> +In-Reply-To: <0> +References: <0> +--- +Message-Id: <2> +In-Reply-To: <1> +References: <0> + <1> +EOF + +test_expect_success 'thread deep' ' + check_threading expect.deep --thread=deep master +' + +cat > expect.deep-irt <<EOF +--- +Message-Id: <0> +In-Reply-To: <1> +References: <1> +--- +Message-Id: <2> +In-Reply-To: <0> +References: <1> + <0> +--- +Message-Id: <3> +In-Reply-To: <2> +References: <1> + <0> + <2> +EOF + +test_expect_success 'thread deep in-reply-to' ' + check_threading expect.deep-irt --thread=deep \ + --in-reply-to="<test.message>" master +' + +cat > expect.deep-cl <<EOF +--- +Message-Id: <0> +--- +Message-Id: <1> +In-Reply-To: <0> +References: <0> +--- +Message-Id: <2> +In-Reply-To: <1> +References: <0> + <1> +--- +Message-Id: <3> +In-Reply-To: <2> +References: <0> + <1> + <2> +EOF + +test_expect_success 'thread deep cover-letter' ' + check_threading expect.deep-cl --cover-letter --thread=deep master +' + +cat > expect.deep-cl-irt <<EOF +--- +Message-Id: <0> +In-Reply-To: <1> +References: <1> +--- +Message-Id: <2> +In-Reply-To: <0> +References: <1> + <0> +--- +Message-Id: <3> +In-Reply-To: <2> +References: <1> + <0> + <2> +--- +Message-Id: <4> +In-Reply-To: <3> +References: <1> + <0> + <2> + <3> +EOF + +test_expect_success 'thread deep cover-letter in-reply-to' ' + check_threading expect.deep-cl-irt --cover-letter \ + --in-reply-to="<test.message>" --thread=deep master +' + +test_expect_success 'thread via config' ' + git config format.thread true && + check_threading expect.thread master +' + +test_expect_success 'thread deep via config' ' + git config format.thread deep && + check_threading expect.deep master +' + +test_expect_success 'thread config + override' ' + git config format.thread deep && + check_threading expect.thread --thread master +' + +test_expect_success 'thread config + --no-thread' ' + git config format.thread deep && + check_threading expect.no-threading --no-thread master ' test_expect_success 'excessive subject' ' @@ -305,4 +505,15 @@ test_expect_success 'format-patch from a subdirectory (3)' ' test -f "$basename" ' +test_expect_success 'format-patch --in-reply-to' ' + git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 && + grep "^In-Reply-To: <baz@foo.bar>" patch8 && + grep "^References: <baz@foo.bar>" patch8 +' + +test_expect_success 'format-patch --signoff' ' + git format-patch -1 --signoff --stdout | + grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +' + test_done diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 0720001281..4ea42e00da 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -136,6 +136,15 @@ test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' ' GIT_EXTERNAL_DIFF=echo git diff ' +test_expect_success 'GIT_EXTERNAL_DIFF generates pretty paths' ' + touch file.ext && + git add file.ext && + echo with extension > file.ext && + GIT_EXTERNAL_DIFF=echo git diff file.ext | grep ......_file\.ext && + git update-index --force-remove file.ext && + rm file.ext +' + echo "#!$SHELL_PATH" >fake-diff.sh cat >> fake-diff.sh <<\EOF cat $2 >> crlfed.txt diff --git a/t/t4021-format-patch-numbered.sh b/t/t4021-format-patch-numbered.sh index 390af2389f..709b3231ca 100755 --- a/t/t4021-format-patch-numbered.sh +++ b/t/t4021-format-patch-numbered.sh @@ -86,6 +86,13 @@ test_expect_success 'format.numbered && --no-numbered' ' ' +test_expect_success 'format.numbered && --keep-subject' ' + + git format-patch --keep-subject --stdout HEAD^ >patch4a && + grep "^Subject: Third" patch4a + +' + test_expect_success 'format.numbered = auto' ' git config format.numbered auto @@ -108,4 +115,10 @@ test_expect_success 'format.numbered = auto && --no-numbered' ' ' +test_expect_success '--start-number && --numbered' ' + + git format-patch --start-number 3 --numbered --stdout HEAD~1 > patch8 && + grep "^Subject: \[PATCH 3/3\]" patch8 +' + test_done diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh index 297ddb5a25..9bdf6596d8 100755 --- a/t/t4023-diff-rename-typechange.sh +++ b/t/t4023-diff-rename-typechange.sh @@ -4,6 +4,12 @@ test_description='typechange rename detection' . ./test-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + test_expect_success setup ' rm -f foo bar && diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh index 9ddbbcde57..3ccc237a8d 100755 --- a/t/t4029-diff-trailing-space.sh +++ b/t/t4029-diff-trailing-space.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # Copyright (c) Jim Meyering # diff --git a/t/t4017-quiet.sh b/t/t4035-diff-quiet.sh index e747e84227..e747e84227 100755 --- a/t/t4017-quiet.sh +++ b/t/t4035-diff-quiet.sh diff --git a/t/t4021-format-patch-signer-mime.sh b/t/t4036-format-patch-signer-mime.sh index ba43f18549..ba43f18549 100755 --- a/t/t4021-format-patch-signer-mime.sh +++ b/t/t4036-format-patch-signer-mime.sh diff --git a/t/t4102-apply-rename.sh b/t/t4102-apply-rename.sh index d42abff1ad..1597965241 100755 --- a/t/t4102-apply-rename.sh +++ b/t/t4102-apply-rename.sh @@ -31,14 +31,16 @@ test_expect_success setup \ test_expect_success apply \ 'git apply --index --stat --summary --apply test-patch' -if [ "$(git config --get core.filemode)" = false ] +if test "$(git config --bool core.filemode)" = false then say 'filemode disabled on the filesystem' else - test_expect_success validate \ - 'test -f bar && ls -l bar | grep "^-..x......"' + test_set_prereq FILEMODE fi +test_expect_success FILEMODE validate \ + 'test -f bar && ls -l bar | grep "^-..x......"' + test_expect_success 'apply reverse' \ 'git apply -R --index --stat --summary --apply test-patch && test "$(cat foo)" = "This is foo"' diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh index 0f185caa44..99ec13dd53 100755 --- a/t/t4114-apply-typechange.sh +++ b/t/t4114-apply-typechange.sh @@ -9,6 +9,12 @@ test_description='git apply should not get confused with type changes. . ./test-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + test_expect_success 'setup repository and commits' ' echo "hello world" > foo && echo "hi planet" > bar && diff --git a/t/t4115-apply-symlink.sh b/t/t4115-apply-symlink.sh index 9ace578f17..b852e58980 100755 --- a/t/t4115-apply-symlink.sh +++ b/t/t4115-apply-symlink.sh @@ -9,6 +9,12 @@ test_description='git apply symlinks and partial files . ./test-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + test_expect_success setup ' ln -s path1/path2/path3/path4/path5 link1 && diff --git a/t/t4118-apply-empty-context.sh b/t/t4118-apply-empty-context.sh index f92e259cc6..65f2e4c3ef 100755 --- a/t/t4118-apply-empty-context.sh +++ b/t/t4118-apply-empty-context.sh @@ -20,10 +20,10 @@ test_expect_success setup ' cat file1 && echo Q | tr -d "\\012" } >file2 && - cat file2 >file2.orig + cat file2 >file2.orig && git add file1 file2 && sed -e "/^B/d" <file1.orig >file1 && - sed -e "/^[BQ]/d" <file2.orig >file2 && + cat file1 > file2 && echo Q | tr -d "\\012" >>file2 && cat file1 >file1.mods && cat file2 >file2.mods && diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh index 841773f75f..0d3c1d5dd5 100755 --- a/t/t4122-apply-symlink-inside.sh +++ b/t/t4122-apply-symlink-inside.sh @@ -3,6 +3,12 @@ test_description='apply to deeper directory without getting fooled with symlink' . ./test-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + lecho () { for l_ do diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh index adfcbb5a3b..fc7af04931 100755 --- a/t/t4129-apply-samemode.sh +++ b/t/t4129-apply-samemode.sh @@ -4,6 +4,13 @@ test_description='applying patch with mode bits' . ./test-lib.sh +if test "$(git config --bool core.filemode)" = false +then + say 'filemode disabled on the filesystem' +else + test_set_prereq FILEMODE +fi + test_expect_success setup ' echo original >file && git add file && @@ -16,14 +23,14 @@ test_expect_success setup ' git diff --stat -p >patch-1.txt ' -test_expect_success 'same mode (no index)' ' +test_expect_success FILEMODE 'same mode (no index)' ' git reset --hard && chmod +x file && git apply patch-0.txt && test -x file ' -test_expect_success 'same mode (with index)' ' +test_expect_success FILEMODE 'same mode (with index)' ' git reset --hard && chmod +x file && git add file && @@ -32,7 +39,7 @@ test_expect_success 'same mode (with index)' ' git diff --exit-code ' -test_expect_success 'same mode (index only)' ' +test_expect_success FILEMODE 'same mode (index only)' ' git reset --hard && chmod +x file && git add file && @@ -40,20 +47,20 @@ test_expect_success 'same mode (index only)' ' git ls-files -s file | grep "^100755" ' -test_expect_success 'mode update (no index)' ' +test_expect_success FILEMODE 'mode update (no index)' ' git reset --hard && git apply patch-1.txt && test -x file ' -test_expect_success 'mode update (with index)' ' +test_expect_success FILEMODE 'mode update (with index)' ' git reset --hard && git apply --index patch-1.txt && test -x file && git diff --exit-code ' -test_expect_success 'mode update (index only)' ' +test_expect_success FILEMODE 'mode update (index only)' ' git reset --hard && git apply --cached patch-1.txt && git ls-files -s file | grep "^100755" diff --git a/t/t4131-apply-fake-ancestor.sh b/t/t4131-apply-fake-ancestor.sh new file mode 100755 index 0000000000..94373ca9a0 --- /dev/null +++ b/t/t4131-apply-fake-ancestor.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# +# Copyright (c) 2009 Stephen Boyd +# + +test_description='git apply --build-fake-ancestor handling.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit 1 && + test_commit 2 && + mkdir sub && + test_commit 3 sub/3 && + test_commit 4 +' + +test_expect_success 'apply --build-fake-ancestor' ' + git checkout 2 && + echo "A" > 1.t && + git diff > 1.patch && + git reset --hard && + git checkout 1 && + git apply --build-fake-ancestor 1.ancestor 1.patch +' + +test_expect_success 'apply --build-fake-ancestor in a subdirectory' ' + git checkout 3 && + echo "C" > sub/3.t && + git diff > 3.patch && + git reset --hard && + git checkout 4 && + ( + cd sub && + git apply --build-fake-ancestor 3.ancestor ../3.patch && + test -f 3.ancestor + ) && + git apply --build-fake-ancestor 3.ancestor 3.patch && + test_cmp sub/3.ancestor 3.ancestor +' + +test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 5e65afa0c1..d6ebbaebe2 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -290,4 +290,19 @@ test_expect_success 'am --ignore-date' ' echo "$at" | grep "+0000" ' +test_expect_success 'am into an unborn branch' ' + rm -fr subdir && + mkdir -p subdir && + git format-patch --numbered-files -o subdir -1 first && + ( + cd subdir && + git init && + git am 1 + ) && + result=$( + cd subdir && git rev-parse HEAD^{tree} + ) && + test "z$result" = "z$(git rev-parse first^{tree})" +' + test_done diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index b68ab11f29..a6bc028a57 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -57,7 +57,7 @@ test_expect_success 'conflicting merge' ' test_must_fail git merge first ' -sha1=$(sed -e 's/ .*//' .git/MERGE_RR) +sha1=$(perl -pe 's/ .*//' .git/MERGE_RR) rr=.git/rr-cache/$sha1 test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage" @@ -190,8 +190,6 @@ test_expect_success 'file2 added differently in two branches' ' git add file2 && git commit -m version2 && test_must_fail git merge fourth && - sha1=$(sed -e "s/ .*//" .git/MERGE_RR) && - rr=.git/rr-cache/$sha1 && echo Cello > file2 && git add file2 && git commit -m resolution diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 7b976ee36d..aad3894ad4 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -37,6 +37,46 @@ test_expect_success setup ' ' +printf "sixth\nfifth\nfourth\nthird\nsecond\ninitial" > expect +test_expect_success 'pretty' ' + + git log --pretty="format:%s" > actual && + test_cmp expect actual +' + +printf "sixth\nfifth\nfourth\nthird\nsecond\ninitial\n" > expect +test_expect_success 'pretty (tformat)' ' + + git log --pretty="tformat:%s" > actual && + test_cmp expect actual +' + +test_expect_success 'pretty (shortcut)' ' + + git log --pretty="%s" > actual && + test_cmp expect actual +' + +test_expect_success 'format' ' + + git log --format="%s" > actual && + test_cmp expect actual +' + +cat > expect << EOF +804a787 sixth +394ef78 fifth +5d31159 fourth +2fbe8c0 third +f7dab8e second +3a2fdcb initial +EOF +test_expect_success 'oneline' ' + + git log --oneline > actual && + test_cmp expect actual +' + test_expect_success 'diff-filter=A' ' actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) && @@ -134,5 +174,177 @@ test_expect_success 'log --grep -i' ' test_cmp expect actual ' +cat > expect <<EOF +* Second +* sixth +* fifth +* fourth +* third +* second +* initial +EOF + +test_expect_success 'simple log --graph' ' + git log --graph --pretty=tformat:%s >actual && + test_cmp expect actual +' + +test_expect_success 'set up merge history' ' + git checkout -b side HEAD~4 && + test_commit side-1 1 1 && + test_commit side-2 2 2 && + git checkout master && + git merge side +' + +cat > expect <<\EOF +* Merge branch 'side' +|\ +| * side-2 +| * side-1 +* | Second +* | sixth +* | fifth +* | fourth +|/ +* third +* second +* initial +EOF + +test_expect_success 'log --graph with merge' ' + git log --graph --date-order --pretty=tformat:%s | + sed "s/ *$//" >actual && + test_cmp expect actual +' + +cat > expect <<\EOF +* commit master +|\ Merge: A B +| | Author: A U Thor <author@example.com> +| | +| | Merge branch 'side' +| | +| * commit side +| | Author: A U Thor <author@example.com> +| | +| | side-2 +| | +| * commit tags/side-1 +| | Author: A U Thor <author@example.com> +| | +| | side-1 +| | +* | commit master~1 +| | Author: A U Thor <author@example.com> +| | +| | Second +| | +* | commit master~2 +| | Author: A U Thor <author@example.com> +| | +| | sixth +| | +* | commit master~3 +| | Author: A U Thor <author@example.com> +| | +| | fifth +| | +* | commit master~4 +|/ Author: A U Thor <author@example.com> +| +| fourth +| +* commit tags/side-1~1 +| Author: A U Thor <author@example.com> +| +| third +| +* commit tags/side-1~2 +| Author: A U Thor <author@example.com> +| +| second +| +* commit tags/side-1~3 + Author: A U Thor <author@example.com> + + initial +EOF + +test_expect_success 'log --graph with full output' ' + git log --graph --date-order --pretty=short | + git name-rev --name-only --stdin | + sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual && + test_cmp expect actual +' + +test_expect_success 'set up more tangled history' ' + git checkout -b tangle HEAD~6 && + test_commit tangle-a tangle-a a && + git merge master~3 && + git merge side~1 && + git checkout master && + git merge tangle && + git checkout -b reach && + test_commit reach && + git checkout master && + git checkout -b octopus-a && + test_commit octopus-a && + git checkout master && + git checkout -b octopus-b && + test_commit octopus-b && + git checkout master && + test_commit seventh && + git merge octopus-a octopus-b + git merge reach +' + +cat > expect <<\EOF +* Merge branch 'reach' +|\ +| \ +| \ +*-. \ Merge branches 'octopus-a' and 'octopus-b' +|\ \ \ +* | | | seventh +| | * | octopus-b +| |/ / +|/| | +| * | octopus-a +|/ / +| * reach +|/ +* Merge branch 'tangle' +|\ +| * Merge branch 'side' (early part) into tangle +| |\ +| * \ Merge branch 'master' (early part) into tangle +| |\ \ +| * | | tangle-a +* | | | Merge branch 'side' +|\ \ \ \ +| * | | | side-2 +| | |_|/ +| |/| | +| * | | side-1 +* | | | Second +* | | | sixth +| |_|/ +|/| | +* | | fifth +* | | fourth +|/ / +* | third +|/ +* second +* initial +EOF + +test_expect_success 'log --graph with merge' ' + git log --graph --date-order --pretty=tformat:%s | + sed "s/ *$//" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh new file mode 100755 index 0000000000..04f7bae850 --- /dev/null +++ b/t/t4204-patch-id.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +test_description='git patch-id' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit initial foo a && + test_commit first foo b && + git checkout -b same HEAD^ && + test_commit same-msg foo b && + git checkout -b notsame HEAD^ && + test_commit notsame-msg foo c +' + +test_expect_success 'patch-id output is well-formed' ' + git log -p -1 | git patch-id > output && + grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output +' + +get_patch_id () { + git log -p -1 "$1" | git patch-id | + sed "s# .*##" > patch-id_"$1" +} + +test_expect_success 'patch-id detects equality' ' + get_patch_id master && + get_patch_id same && + test_cmp patch-id_master patch-id_same +' + +test_expect_success 'patch-id detects inequality' ' + get_patch_id master && + get_patch_id notsame && + ! test_cmp patch-id_master patch-id_notsame +' + +test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index c942c8be85..abb41b07ef 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -37,7 +37,11 @@ test_expect_success \ cp /bin/sh a/bin && printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 && printf "A not substituted O" >a/substfile2 && - ln -s a a/l1 && + if test_have_prereq SYMLINKS; then + ln -s a a/l1 + else + printf %s a > a/l1 + fi && (p=long_path_to_a_file && cd a && for depth in 1 2 3 4 5; do mkdir $p && cd $p; done && echo text >file_with_long_path) && @@ -46,7 +50,7 @@ test_expect_success \ test_expect_success \ 'add ignored file' \ 'echo ignore me >a/ignored && - echo ignored export-ignore >.gitattributes' + echo ignored export-ignore >.git/info/attributes' test_expect_success \ 'add files to repository' \ @@ -60,7 +64,7 @@ test_expect_success \ test_expect_success \ 'create bare clone' \ 'git clone --bare . bare.git && - cp .gitattributes bare.git/info/attributes' + cp .git/info/attributes bare.git/info/attributes' test_expect_success \ 'remove ignored file' \ @@ -76,7 +80,7 @@ test_expect_success \ test_expect_success \ 'git archive vs. git tar-tree' \ - 'diff b.tar b2.tar' + 'test_cmp b.tar b2.tar' test_expect_success \ 'git archive in a bare repo' \ @@ -86,18 +90,22 @@ test_expect_success \ 'git archive vs. the same in a bare repo' \ 'test_cmp b.tar b3.tar' +test_expect_success 'git archive with --output' \ + 'git archive --output=b4.tar HEAD && + test_cmp b.tar b4.tar' + test_expect_success \ 'validate file modification time' \ 'mkdir extract && "$TAR" xf b.tar -C extract a/a && test-chmtime -v +0 extract/a/a |cut -f 1 >b.mtime && echo "1117231200" >expected.mtime && - diff expected.mtime b.mtime' + test_cmp expected.mtime b.mtime' test_expect_success \ 'git get-tar-commit-id' \ 'git get-tar-commit-id <b.tar >b.commitid && - diff .git/$(git symbolic-ref HEAD) b.commitid' + test_cmp .git/$(git symbolic-ref HEAD) b.commitid' test_expect_success \ 'extract tar archive' \ @@ -106,7 +114,7 @@ test_expect_success \ test_expect_success \ 'validate filenames' \ '(cd b/a && find .) | sort >b.lst && - diff a.lst b.lst' + test_cmp a.lst b.lst' test_expect_success \ 'validate file contents' \ @@ -123,7 +131,7 @@ test_expect_success \ test_expect_success \ 'validate filenames with prefix' \ '(cd c/prefix/a && find .) | sort >c.lst && - diff a.lst c.lst' + test_cmp a.lst c.lst' test_expect_success \ 'validate file contents with prefix' \ @@ -131,10 +139,11 @@ test_expect_success \ test_expect_success \ 'create archives with substfiles' \ - 'echo "substfile?" export-subst >a/.gitattributes && + 'cp .git/info/attributes .git/info/attributes.before && + echo "substfile?" export-subst >>.git/info/attributes && git archive HEAD >f.tar && git archive --prefix=prefix/ HEAD >g.tar && - rm a/.gitattributes' + mv .git/info/attributes.before .git/info/attributes' test_expect_success \ 'extract substfiles' \ @@ -144,8 +153,8 @@ test_expect_success \ 'validate substfile contents' \ 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ >f/a/substfile1.expected && - diff f/a/substfile1.expected f/a/substfile1 && - diff a/substfile2 f/a/substfile2 + test_cmp f/a/substfile1.expected f/a/substfile1 && + test_cmp a/substfile2 f/a/substfile2 ' test_expect_success \ @@ -156,8 +165,8 @@ test_expect_success \ 'validate substfile contents from archive with prefix' \ 'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \ >g/prefix/a/substfile1.expected && - diff g/prefix/a/substfile1.expected g/prefix/a/substfile1 && - diff a/substfile2 g/prefix/a/substfile2 + test_cmp g/prefix/a/substfile1.expected g/prefix/a/substfile1 && + test_cmp a/substfile2 g/prefix/a/substfile2 ' test_expect_success \ @@ -172,23 +181,27 @@ test_expect_success \ 'git archive --format=zip vs. the same in a bare repo' \ 'test_cmp d.zip d1.zip' +test_expect_success 'git archive --format=zip with --output' \ + 'git archive --format=zip --output=d2.zip HEAD && + test_cmp d.zip d2.zip' + $UNZIP -v >/dev/null 2>&1 if [ $? -eq 127 ]; then - echo "Skipping ZIP tests, because unzip was not found" - test_done - exit + say "Skipping ZIP tests, because unzip was not found" +else + test_set_prereq UNZIP fi -test_expect_success \ +test_expect_success UNZIP \ 'extract ZIP archive' \ '(mkdir d && cd d && $UNZIP ../d.zip)' -test_expect_success \ +test_expect_success UNZIP \ 'validate filenames' \ '(cd d/a && find .) | sort >d.lst && - diff a.lst d.lst' + test_cmp a.lst d.lst' -test_expect_success \ +test_expect_success UNZIP \ 'validate file contents' \ 'diff -r a d/a' @@ -196,16 +209,16 @@ test_expect_success \ 'git archive --format=zip with prefix' \ 'git archive --format=zip --prefix=prefix/ HEAD >e.zip' -test_expect_success \ +test_expect_success UNZIP \ 'extract ZIP archive with prefix' \ '(mkdir e && cd e && $UNZIP ../e.zip)' -test_expect_success \ +test_expect_success UNZIP \ 'validate filenames with prefix' \ '(cd e/prefix/a && find .) | sort >e.lst && - diff a.lst e.lst' + test_cmp a.lst e.lst' -test_expect_success \ +test_expect_success UNZIP \ 'validate file contents with prefix' \ 'diff -r a e/prefix/a' diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh new file mode 100755 index 0000000000..426b319bd3 --- /dev/null +++ b/t/t5001-archive-attr.sh @@ -0,0 +1,91 @@ +#!/bin/sh + +test_description='git archive attribute tests' + +. ./test-lib.sh + +SUBSTFORMAT=%H%n + +test_expect_exists() { + test_expect_success " $1 exists" "test -e $1" +} + +test_expect_missing() { + test_expect_success " $1 does not exist" "test ! -e $1" +} + +test_expect_success 'setup' ' + echo ignored >ignored && + echo ignored export-ignore >>.git/info/attributes && + git add ignored && + + echo ignored by tree >ignored-by-tree && + echo ignored-by-tree export-ignore >.gitattributes && + git add ignored-by-tree .gitattributes && + + echo ignored by worktree >ignored-by-worktree && + echo ignored-by-worktree export-ignore >.gitattributes && + git add ignored-by-worktree && + + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >nosubstfile && + printf "A\$Format:%s\$O" "$SUBSTFORMAT" >substfile1 && + printf "A not substituted O" >substfile2 && + echo "substfile?" export-subst >>.git/info/attributes && + git add nosubstfile substfile1 substfile2 && + + git commit -m. && + + git clone --bare . bare && + cp .git/info/attributes bare/info/attributes +' + +test_expect_success 'git archive' ' + git archive HEAD >archive.tar && + (mkdir archive && cd archive && "$TAR" xf -) <archive.tar +' + +test_expect_missing archive/ignored +test_expect_missing archive/ignored-by-tree +test_expect_exists archive/ignored-by-worktree + +test_expect_success 'git archive with worktree attributes' ' + git archive --worktree-attributes HEAD >worktree.tar && + (mkdir worktree && cd worktree && "$TAR" xf -) <worktree.tar +' + +test_expect_missing worktree/ignored +test_expect_exists worktree/ignored-by-tree +test_expect_missing worktree/ignored-by-worktree + +test_expect_success 'git archive vs. bare' ' + (cd bare && git archive HEAD) >bare-archive.tar && + test_cmp archive.tar bare-archive.tar +' + +test_expect_success 'git archive with worktree attributes, bare' ' + (cd bare && git archive --worktree-attributes HEAD) >bare-worktree.tar && + (mkdir bare-worktree && cd bare-worktree && "$TAR" xf -) <bare-worktree.tar +' + +test_expect_missing bare-worktree/ignored +test_expect_exists bare-worktree/ignored-by-tree +test_expect_exists bare-worktree/ignored-by-worktree + +test_expect_success 'export-subst' ' + git log "--pretty=format:A${SUBSTFORMAT}O" HEAD >substfile1.expected && + test_cmp nosubstfile archive/nosubstfile && + test_cmp substfile1.expected archive/substfile1 && + test_cmp substfile2 archive/substfile2 +' + +test_expect_success 'git tar-tree vs. git archive with worktree attributes' ' + git tar-tree HEAD >tar-tree.tar && + test_cmp worktree.tar tar-tree.tar +' + +test_expect_success 'git tar-tree vs. git archive with worktree attrs, bare' ' + (cd bare && git tar-tree HEAD) >bare-tar-tree.tar && + test_cmp bare-worktree.tar bare-tar-tree.tar +' + +test_done diff --git a/t/t5100/rfc2047-samples.mbox b/t/t5100/rfc2047-samples.mbox index 3ca2470da2..1fc224810d 100644 --- a/t/t5100/rfc2047-samples.mbox +++ b/t/t5100/rfc2047-samples.mbox @@ -1,48 +1,48 @@ From nobody Mon Sep 17 00:00:00 2001 From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu> -To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk> -CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be> -Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= - =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= +To: =?ISO8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk> +CC: =?ISO8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be> +Subject: =?ISO8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= + =?ISO8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= From nobody Mon Sep 17 00:00:00 2001 -From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se> +From: =?ISO8859-1?Q?Olle_J=E4rnefors?= <ojarnef@admin.kth.se> To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se Subject: Time for ISO 10646? From nobody Mon Sep 17 00:00:00 2001 To: Dave Crocker <dcrocker@mordor.stanford.edu> Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se -From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se> +From: =?ISO8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se> Subject: Re: RFC-HDR care and feeding From nobody Mon Sep 17 00:00:00 2001 From: Nathaniel Borenstein <nsb@thumper.bellcore.com> - (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) + (=?ISO8859-8?b?7eXs+SDv4SDp7Oj08A==?=) To: Greg Vaudreuil <gvaudre@NRI.Reston.VA.US>, Ned Freed <ned@innosoft.com>, Keith Moore <moore@cs.utk.edu> Subject: Test of new header generator MIME-Version: 1.0 -Content-type: text/plain; charset=ISO-8859-1 +Content-type: text/plain; charset=ISO8859-1 From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a?=) +Subject: (=?ISO8859-1?Q?a?=) From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a?= b) +Subject: (=?ISO8859-1?Q?a?= b) From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) +Subject: (=?ISO8859-1?Q?a?= =?ISO8859-1?Q?b?=) From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) +Subject: (=?ISO8859-1?Q?a?= =?ISO8859-1?Q?b?=) From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a?= - =?ISO-8859-1?Q?b?=) +Subject: (=?ISO8859-1?Q?a?= + =?ISO8859-1?Q?b?=) From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a_b?=) +Subject: (=?ISO8859-1?Q?a_b?=) From nobody Mon Sep 17 00:00:00 2001 -Subject: (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=) +Subject: (=?ISO8859-1?Q?a?= =?ISO8859-2?Q?_b?=) diff --git a/t/t5100/sample.mbox b/t/t5100/sample.mbox index c5ad206b40..c3074ac573 100644 --- a/t/t5100/sample.mbox +++ b/t/t5100/sample.mbox @@ -99,7 +99,7 @@ index 9123cdc..918dcf8 100644 From nobody Sat Aug 27 23:07:49 2005 Path: news.gmane.org!not-for-mail Message-ID: <20050721.091036.01119516.yoshfuji@linux-ipv6.org> -From: YOSHIFUJI Hideaki / =?iso-2022-jp?B?GyRCNUhGIzFRTEAbKEI=?= +From: YOSHIFUJI Hideaki / =?ISO-2022-JP?B?GyRCNUhGIzFRTEAbKEI=?= <yoshfuji@linux-ipv6.org> Newsgroups: gmane.comp.version-control.git Subject: [PATCH 1/2] GIT: Try all addresses for given remote name @@ -218,7 +218,7 @@ GPG-FP : 9022 65EB 1ECF 3AD1 0BDF 80D8 4807 F894 E062 0EEA From nobody Sat Aug 27 23:07:49 2005 Path: news.gmane.org!not-for-mail Message-ID: <u5tacjjdpxq.fsf@lysator.liu.se> -From: =?iso-8859-1?Q?David_K=E5gedal?= <davidk@lysator.liu.se> +From: =?ISO8859-1?Q?David_K=E5gedal?= <davidk@lysator.liu.se> Newsgroups: gmane.comp.version-control.git Subject: [PATCH] Fixed two bugs in git-cvsimport-script. Date: Mon, 15 Aug 2005 20:18:25 +0200 @@ -226,7 +226,7 @@ Lines: 83 Approved: news@gmane.org NNTP-Posting-Host: main.gmane.org Mime-Version: 1.0 -Content-Type: text/plain; charset=iso-8859-1 +Content-Type: text/plain; charset=ISO8859-1 Content-Transfer-Encoding: QUOTED-PRINTABLE X-Trace: sea.gmane.org 1124130247 31839 80.91.229.2 (15 Aug 2005 18:24:07 GMT) X-Complaints-To: usenet@sea.gmane.org @@ -476,7 +476,7 @@ MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= -Content-Type: text/plain; charset=iso-8859-15 +Content-Type: text/plain; charset=ISO8859-15 Content-Transfer-Encoding: quoted-printable Here comes a commit log message, and diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index ccfc64c6ee..e2aa254eae 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -13,11 +13,10 @@ TRASH=`pwd` test_expect_success \ 'setup' \ 'rm -f .git/index* - for i in a b c - do - dd if=/dev/zero bs=4k count=1 | perl -pe "y/\\000/$i/" >$i && - git update-index --add $i || return 1 - done && + perl -e "print \"a\" x 4096;" > a && + perl -e "print \"b\" x 4096;" > b && + perl -e "print \"c\" x 4096;" > c && + git update-index --add a b c && cat c >d && echo foo >>d && git update-index --add d && tree=`git write-tree` && commit=`git commit-tree $tree </dev/null` && { @@ -221,7 +220,7 @@ test_expect_success \ test_expect_success \ 'verify-pack catches a corrupted pack signature' \ 'cat test-1-${packname_1}.pack >test-3.pack && - dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 && + echo | dd of=test-3.pack count=1 bs=1 conv=notrunc seek=2 && if git verify-pack test-3.idx then false else :; @@ -230,7 +229,7 @@ test_expect_success \ test_expect_success \ 'verify-pack catches a corrupted pack version' \ 'cat test-1-${packname_1}.pack >test-3.pack && - dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 && + echo | dd of=test-3.pack count=1 bs=1 conv=notrunc seek=7 && if git verify-pack test-3.idx then false else :; @@ -239,7 +238,7 @@ test_expect_success \ test_expect_success \ 'verify-pack catches a corrupted type/size of the 1st packed object data' \ 'cat test-1-${packname_1}.pack >test-3.pack && - dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 && + echo | dd of=test-3.pack count=1 bs=1 conv=notrunc seek=12 && if git verify-pack test-3.idx then false else :; @@ -250,7 +249,7 @@ test_expect_success \ 'l=`wc -c <test-3.idx` && l=`expr $l - 20` && cat test-1-${packname_1}.pack >test-3.pack && - dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l && + printf "%20s" "" | dd of=test-3.idx count=20 bs=1 conv=notrunc seek=$l && if git verify-pack test-3.pack then false else :; diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index e6f70d474f..4360e77d31 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -69,32 +69,27 @@ test_expect_success \ 'index v2: force some 64-bit offsets with pack-objects' \ 'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)' -have_64bits= if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) || ! (echo "$msg" | grep "pack too large .* off_t") then - have_64bits=t + test_set_prereq OFF64_T else say "skipping tests concerning 64-bit offsets" fi -test "$have_64bits" && -test_expect_success \ +test_expect_success OFF64_T \ 'index v2: verify a pack with some 64-bit offsets' \ 'git verify-pack -v "test-3-${pack3}.pack"' -test "$have_64bits" && -test_expect_success \ +test_expect_success OFF64_T \ '64-bit offsets: should be different from previous index v2 results' \ '! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"' -test "$have_64bits" && -test_expect_success \ +test_expect_success OFF64_T \ 'index v2: force some 64-bit offsets with index-pack' \ 'git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"' -test "$have_64bits" && -test_expect_success \ +test_expect_success OFF64_T \ '64-bit offsets: index-pack result should match pack-objects one' \ 'cmp "test-3-${pack3}.idx" "3.idx"' @@ -208,7 +203,7 @@ test_expect_success \ obj=`git hash-object file_001` && nr=`index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj` && chmod +w ".git/objects/pack/pack-${pack1}.idx" && - dd if=/dev/zero of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ + printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \ bs=1 count=4 seek=$((8 + 256 * 4 + `wc -l <obj-list` * 20 + $nr * 4)) && ( while read obj do git cat-file -p $obj >/dev/null || exit 1 diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index d4e30fc43c..5132d41309 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -55,6 +55,8 @@ do_corrupt_object() { test_must_fail git verify-pack ${pack}.pack } +printf '\0' > zero + test_expect_success \ 'initial setup validation' \ 'create_test_files && @@ -66,7 +68,7 @@ test_expect_success \ test_expect_success \ 'create corruption in header of first object' \ - 'do_corrupt_object $blob_1 0 < /dev/zero && + 'do_corrupt_object $blob_1 0 < zero && test_must_fail git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null' @@ -125,7 +127,7 @@ test_expect_success \ 'create corruption in header of first delta' \ 'create_new_pack && git prune-packed && - do_corrupt_object $blob_2 0 < /dev/zero && + do_corrupt_object $blob_2 0 < zero && git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null' @@ -180,7 +182,7 @@ test_expect_success \ 'corruption in delta base reference of first delta (OBJ_REF_DELTA)' \ 'create_new_pack && git prune-packed && - do_corrupt_object $blob_2 2 < /dev/zero && + do_corrupt_object $blob_2 2 < zero && git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null' @@ -207,7 +209,7 @@ test_expect_success \ 'corruption #0 in delta base reference of first delta (OBJ_OFS_DELTA)' \ 'create_new_pack --delta-base-offset && git prune-packed && - do_corrupt_object $blob_2 2 < /dev/zero && + do_corrupt_object $blob_2 2 < zero && git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null' @@ -259,7 +261,7 @@ test_expect_success \ test_expect_success \ '... and a redundant pack allows for full recovery too' \ - 'do_corrupt_object $blob_2 2 < /dev/zero && + 'do_corrupt_object $blob_2 2 < zero && git cat-file blob $blob_1 > /dev/null && test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null && diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 9b2e1a94c5..5858b868ed 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -71,4 +71,18 @@ test_expect_success 'post-checkout receives the right args when not switching br test $old = $new -a $flag = 0 ' +if test "$(git config --bool core.filemode)" = true; then +mkdir -p templates/hooks +cat >templates/hooks/post-checkout <<'EOF' +#!/bin/sh +echo $@ > $GIT_DIR/post-checkout.args +EOF +chmod +x templates/hooks/post-checkout + +test_expect_success 'post-checkout hook is triggered by clone' ' + git clone --template=templates . clone3 && + test -f clone3/.git/post-checkout.args +' +fi + test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index c450f33f33..a8c2ca2a78 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -3,9 +3,8 @@ # Copyright (c) 2005 Johannes Schindelin # -test_description='Testing multi_ack pack fetching +test_description='Testing multi_ack pack fetching' -' . ./test-lib.sh # Test fetch-pack/upload-pack pair. @@ -13,77 +12,60 @@ test_description='Testing multi_ack pack fetching # Some convenience functions add () { - name=$1 - text="$@" - branch=`echo $name | sed -e 's/^\(.\).*$/\1/'` - parents="" + name=$1 && + text="$@" && + branch=`echo $name | sed -e 's/^\(.\).*$/\1/'` && + parents="" && - shift + shift && while test $1; do - parents="$parents -p $1" + parents="$parents -p $1" && shift - done + done && - echo "$text" > test.txt - git update-index --add test.txt - tree=$(git write-tree) + echo "$text" > test.txt && + git update-index --add test.txt && + tree=$(git write-tree) && # make sure timestamps are in correct order - sec=$(($sec+1)) - commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \ - git commit-tree $tree $parents 2>>log2.txt) - eval "$name=$commit; export $name" - echo $commit > .git/refs/heads/$branch + test_tick && + commit=$(echo "$text" | git commit-tree $tree $parents) && + eval "$name=$commit; export $name" && + echo $commit > .git/refs/heads/$branch && eval ${branch}TIP=$commit } -count_objects () { - ls .git/objects/??/* 2>>log2.txt | wc -l | tr -d " " -} - -test_expect_object_count () { - message=$1 - count=$2 - - output="$(count_objects)" - test_expect_success \ - "new object count $message" \ - "test $count = $output" -} - pull_to_client () { - number=$1 - heads=$2 - count=$3 - no_strict_count_check=$4 - - cd client - test_expect_success "$number pull" \ - "git fetch-pack -k -v .. $heads" - case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac - case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac - git symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'` - - test_expect_success "fsck" 'git fsck --full > fsck.txt 2>&1' - - test_expect_success 'check downloaded results' \ - 'mv .git/objects/pack/pack-* . && - p=`ls -1 pack-*.pack` && - git unpack-objects <$p && - git fsck --full' - - test_expect_success "new object count after $number pull" \ - 'idx=`echo pack-*.idx` && - pack_count=`git show-index <$idx | wc -l` && - test $pack_count = $count' - test -z "$pack_count" && pack_count=0 - if [ -z "$no_strict_count_check" ]; then - test_expect_success "minimal count" "test $count = $pack_count" - else - test $count != $pack_count && \ - echo "WARNING: $pack_count objects transmitted, only $count of which were needed" - fi - rm -f pack-* - cd .. + number=$1 && + heads=$2 && + count=$3 && + test_expect_success "$number pull" ' + ( + cd client && + git fetch-pack -k -v .. $heads && + + case "$heads" in + *A*) + echo $ATIP > .git/refs/heads/A;; + esac && + case "$heads" in *B*) + echo $BTIP > .git/refs/heads/B;; + esac && + git symbolic-ref HEAD refs/heads/`echo $heads \ + | sed -e "s/^\(.\).*$/\1/"` && + + git fsck --full && + + mv .git/objects/pack/pack-* . && + p=`ls -1 pack-*.pack` && + git unpack-objects <$p && + git fsck --full && + + idx=`echo pack-*.idx` && + pack_count=`git show-index <$idx | wc -l` && + test $pack_count = $count && + rm -f pack-* + ) + ' } # Here begins the actual testing @@ -94,89 +76,129 @@ pull_to_client () { # client pulls A20, B1. Then tracks only B. Then pulls A. -( +test_expect_success 'setup' ' mkdir client && - cd client && - git init 2>> log2.txt && - git config transfer.unpacklimit 0 -) - -add A1 - -prev=1; cur=2; while [ $cur -le 10 ]; do - add A$cur $(eval echo \$A$prev) - prev=$cur - cur=$(($cur+1)) -done - -add B1 $A1 - -echo $ATIP > .git/refs/heads/A -echo $BTIP > .git/refs/heads/B -git symbolic-ref HEAD refs/heads/B + ( + cd client && + git init && + git config transfer.unpacklimit 0 + ) && + add A1 && + prev=1 && + cur=2 && + while [ $cur -le 10 ]; do + add A$cur $(eval echo \$A$prev) && + prev=$cur && + cur=$(($cur+1)) + done && + add B1 $A1 + echo $ATIP > .git/refs/heads/A && + echo $BTIP > .git/refs/heads/B && + git symbolic-ref HEAD refs/heads/B +' pull_to_client 1st "B A" $((11*3)) -add A11 $A10 - -prev=1; cur=2; while [ $cur -le 65 ]; do - add B$cur $(eval echo \$B$prev) - prev=$cur - cur=$(($cur+1)) -done +test_expect_success 'post 1st pull setup' ' + add A11 $A10 && + prev=1 && + cur=2 && + while [ $cur -le 65 ]; do + add B$cur $(eval echo \$B$prev) && + prev=$cur && + cur=$(($cur+1)) + done +' pull_to_client 2nd "B" $((64*3)) -pull_to_client 3rd "A" $((1*3)) # old fails - -test_expect_success "clone shallow" 'git clone --depth 2 "file://$(pwd)/." shallow' +pull_to_client 3rd "A" $((1*3)) -(cd shallow; git count-objects -v) > count.shallow - -test_expect_success "clone shallow object count" \ - "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\"" - -count_output () { - sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/^size-pack:/d' -e '/: 0$/d' "$1" -} +test_expect_success 'clone shallow' ' + git clone --depth 2 "file://$(pwd)/." shallow +' -test_expect_success "clone shallow object count (part 2)" ' - test -z "$(count_output count.shallow)" +test_expect_success 'clone shallow object count' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow && + grep "^in-pack: 18" count.shallow ' -test_expect_success "fsck in shallow repo" \ - "(cd shallow; git fsck --full)" +test_expect_success 'clone shallow object count (part 2)' ' + sed -e "/^in-pack:/d" -e "/^packs:/d" -e "/^size-pack:/d" \ + -e "/: 0$/d" count.shallow > count_output && + ! test -s count_output +' -#test_done; exit +test_expect_success 'fsck in shallow repo' ' + ( + cd shallow && + git fsck --full + ) +' -add B66 $B65 -add B67 $B66 +test_expect_success 'add two more' ' + add B66 $B65 && + add B67 $B66 +' -test_expect_success "pull in shallow repo" \ - "(cd shallow; git pull .. B)" +test_expect_success 'pull in shallow repo' ' + ( + cd shallow && + git pull .. B + ) +' -(cd shallow; git count-objects -v) > count.shallow -test_expect_success "clone shallow object count" \ - "test \"count: 6\" = \"$(grep count count.shallow)\"" +test_expect_success 'clone shallow object count' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow && + grep "^count: 6" count.shallow +' -add B68 $B67 -add B69 $B68 +test_expect_success 'add two more (part 2)' ' + add B68 $B67 && + add B69 $B68 +' -test_expect_success "deepening pull in shallow repo" \ - "(cd shallow; git pull --depth 4 .. B)" +test_expect_success 'deepening pull in shallow repo' ' + ( + cd shallow && + git pull --depth 4 .. B + ) +' -(cd shallow; git count-objects -v) > count.shallow -test_expect_success "clone shallow object count" \ - "test \"count: 12\" = \"$(grep count count.shallow)\"" +test_expect_success 'clone shallow object count' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow && + grep "^count: 12" count.shallow +' -test_expect_success "deepening fetch in shallow repo" \ - "(cd shallow; git fetch --depth 4 .. A:A)" +test_expect_success 'deepening fetch in shallow repo' ' + ( + cd shallow && + git fetch --depth 4 .. A:A + ) +' -(cd shallow; git count-objects -v) > count.shallow -test_expect_success "clone shallow object count" \ - "test \"count: 18\" = \"$(grep count count.shallow)\"" +test_expect_success 'clone shallow object count' ' + ( + cd shallow && + git count-objects -v + ) > count.shallow && + grep "^count: 18" count.shallow +' -test_expect_success "pull in shallow repo with missing merge base" \ - "(cd shallow && test_must_fail git pull --depth 4 .. A)" +test_expect_success 'pull in shallow repo with missing merge base' ' + ( + cd shallow && + test_must_fail git pull --depth 4 .. A + ) +' test_done diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 4074e23ffa..d5db75d826 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -4,6 +4,12 @@ test_description='test automatic tag following' . ./test-lib.sh +case $(uname -s) in +*MINGW*) + say "GIT_DEBUG_SEND_PACK not supported - skipping tests" + test_done +esac + # End state of the repository: # # T - tag1 S - tag2 diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index eb637184a0..e70246b3fb 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -28,7 +28,7 @@ tokens_match () { } check_remote_track () { - actual=$(git remote show "$1" | sed -e '1,/Tracked/d') && + actual=$(git remote show "$1" | sed -ne 's|^ \(.*\) tracked$|\1|p') shift && tokens_match "$*" "$actual" } @@ -136,47 +136,73 @@ EOF cat > test/expect << EOF * remote origin URL: $(pwd)/one - Remote branch merged with 'git pull' while on branch master + HEAD branch: master + Remote branches: + master new (next fetch will store in remotes/origin) + side tracked + Local branches configured for 'git pull': + ahead merges with remote master + master merges with remote master + octopus merges with remote topic-a + and with remote topic-b + and with remote topic-c + rebase rebases onto remote master + Local refs configured for 'git push': + master pushes to master (local out of date) + master pushes to upstream (create) +* remote two + URL: ../two + HEAD branch (remote HEAD is ambiguous, may be one of the following): + another master - New remote branch (next fetch will store in remotes/origin) - master - Tracked remote branches - side - master - Local branches pushed with 'git push' - master:upstream - +refs/tags/lastbackup + Local refs configured for 'git push': + ahead forces to master (fast forwardable) + master pushes to another (up to date) EOF test_expect_success 'show' ' (cd test && - git config --add remote.origin.fetch \ - refs/heads/master:refs/heads/upstream && + git config --add remote.origin.fetch refs/heads/master:refs/heads/upstream && git fetch && + git checkout -b ahead origin/master && + echo 1 >> file && + test_tick && + git commit -m update file && + git checkout master && + git branch --track octopus origin/master && + git branch --track rebase origin/master && git branch -d -r origin/master && + git config --add remote.two.url ../two && + git config branch.rebase.rebase true && + git config branch.octopus.merge "topic-a topic-b topic-c" && (cd ../one && echo 1 > file && test_tick && git commit -m update file) && - git config remote.origin.push \ - refs/heads/master:refs/heads/upstream && - git config --add remote.origin.push \ - +refs/tags/lastbackup && - git remote show origin > output && + git config --add remote.origin.push : && + git config --add remote.origin.push refs/heads/master:refs/heads/upstream && + git config --add remote.origin.push +refs/tags/lastbackup && + git config --add remote.two.push +refs/heads/ahead:refs/heads/master && + git config --add remote.two.push refs/heads/master:refs/heads/another && + git remote show origin two > output && + git branch -d rebase octopus && test_cmp expect output) ' cat > test/expect << EOF * remote origin URL: $(pwd)/one - Remote branch merged with 'git pull' while on branch master - master - Tracked remote branches + HEAD branch: (not queried) + Remote branches: (status not queried) master side - Local branches pushed with 'git push' - master:upstream - +refs/tags/lastbackup + Local branches configured for 'git pull': + ahead merges with remote master + master merges with remote master + Local refs configured for 'git push' (status not queried): + (matching) pushes to (matching) + refs/heads/master pushes to refs/heads/upstream + refs/tags/lastbackup forces to refs/tags/lastbackup EOF test_expect_success 'show -n' ' @@ -197,6 +223,46 @@ test_expect_success 'prune' ' test_must_fail git rev-parse refs/remotes/origin/side) ' +test_expect_success 'set-head --delete' ' + (cd test && + git symbolic-ref refs/remotes/origin/HEAD && + git remote set-head --delete origin && + test_must_fail git symbolic-ref refs/remotes/origin/HEAD) +' + +test_expect_success 'set-head --auto' ' + (cd test && + git remote set-head --auto origin && + echo refs/remotes/origin/master >expect && + git symbolic-ref refs/remotes/origin/HEAD >output && + test_cmp expect output + ) +' + +cat >test/expect <<EOF +error: Multiple remote HEAD branches. Please choose one explicitly with: + git remote set-head two another + git remote set-head two master +EOF + +test_expect_success 'set-head --auto fails w/multiple HEADs' ' + (cd test && + test_must_fail git remote set-head --auto two >output 2>&1 && + test_cmp expect output) +' + +cat >test/expect <<EOF +refs/remotes/origin/side2 +EOF + +test_expect_success 'set-head explicit' ' + (cd test && + git remote set-head origin side2 && + git symbolic-ref refs/remotes/origin/HEAD >output && + git remote set-head origin master && + test_cmp expect output) +' + cat > test/expect << EOF Pruning origin URL: $(pwd)/one @@ -343,7 +409,7 @@ test_expect_success '"remote show" does not show symbolic refs' ' git clone one three && (cd three && git remote show origin > output && - ! grep HEAD < output && + ! grep "^ *HEAD$" < output && ! grep -i stale < output) ' @@ -428,5 +494,15 @@ test_expect_success 'remote prune to cause a dangling symref' ' grep "dangling symref" err ' +test_expect_success 'show empty remote' ' + + test_create_repo empty && + git clone empty empty-clone && + ( + cd empty-clone && + git remote show origin + ) +' + test_done diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh new file mode 100755 index 0000000000..2a1806b0b4 --- /dev/null +++ b/t/t5506-remote-groups.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git remote group handling' +. ./test-lib.sh + +mark() { + echo "$1" >mark +} + +update_repo() { + (cd $1 && + echo content >>file && + git add file && + git commit -F ../mark) +} + +update_repos() { + update_repo one $1 && + update_repo two $1 +} + +repo_fetched() { + if test "`git log -1 --pretty=format:%s $1 --`" = "`cat mark`"; then + echo >&2 "repo was fetched: $1" + return 0 + fi + echo >&2 "repo was not fetched: $1" + return 1 +} + +test_expect_success 'setup' ' + mkdir one && (cd one && git init) && + mkdir two && (cd two && git init) && + git remote add -m master one one && + git remote add -m master two two +' + +test_expect_success 'no group updates all' ' + mark update-all && + update_repos && + git remote update && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'nonexistant group produces error' ' + mark nonexistant && + update_repos && + test_must_fail git remote update nonexistant && + ! repo_fetched one && + ! repo_fetched two +' + +test_expect_success 'updating group updates all members' ' + mark group-all && + update_repos && + git config --add remotes.all one && + git config --add remotes.all two && + git remote update all && + repo_fetched one && + repo_fetched two +' + +test_expect_success 'updating group does not update non-members' ' + mark group-some && + update_repos && + git config --add remotes.some one && + git remote update some && + repo_fetched one && + ! repo_fetched two +' + +test_expect_success 'updating remote name updates that remote' ' + mark remote-name && + update_repos && + git remote update one && + repo_fetched one && + ! repo_fetched two +' + +test_done diff --git a/t/t5511-refspec.sh b/t/t5511-refspec.sh index 22ba380034..c28932216b 100755 --- a/t/t5511-refspec.sh +++ b/t/t5511-refspec.sh @@ -72,4 +72,16 @@ test_refspec fetch ':refs/remotes/frotz/HEAD-to-me' test_refspec push ':refs/remotes/frotz/delete me' invalid test_refspec fetch ':refs/remotes/frotz/HEAD to me' invalid +test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid +test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid + +test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' invalid +test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' invalid + +test_refspec fetch 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid +test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid + +test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*' +test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*' + test_done diff --git a/t/t5515-fetch-merge-logic.sh b/t/t5515-fetch-merge-logic.sh index 1f4608d8ba..dbb927dec8 100755 --- a/t/t5515-fetch-merge-logic.sh +++ b/t/t5515-fetch-merge-logic.sh @@ -129,8 +129,7 @@ do '' | '#'*) continue ;; esac test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'` - cnt=`expr $test_count + 1` - pfx=`printf "%04d" $cnt` + pfx=`printf "%04d" $test_count` expect_f="$TEST_DIRECTORY/t5515/fetch.$test" actual_f="$pfx-fetch.$test" expect_r="$TEST_DIRECTORY/t5515/refs.$test" diff --git a/t/t5521-pull-symlink.sh b/t/t5522-pull-symlink.sh index 5672b51e2e..86bbd7d024 100755 --- a/t/t5521-pull-symlink.sh +++ b/t/t5522-pull-symlink.sh @@ -4,6 +4,12 @@ test_description='pulling from symlinked subdir' . ./test-lib.sh +if ! test_have_prereq SYMLINKS +then + say 'Symbolic links not supported, skipping tests.' + test_done +fi + # The scenario we are building: # # trash\ directory/ diff --git a/t/t5540-http-push.sh b/t/t5540-http-push.sh index 10e5fd0d5a..f4a2cf6c17 100755 --- a/t/t5540-http-push.sh +++ b/t/t5540-http-push.sh @@ -11,22 +11,16 @@ This test runs various sanity checks on http-push.' ROOT_PATH="$PWD" LIB_HTTPD_DAV=t +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'} if git http-push > /dev/null 2>&1 || [ $? -eq 128 ] then say "skipping test, USE_CURL_MULTI is not defined" test_done - exit fi . "$TEST_DIRECTORY"/lib-httpd.sh - -if ! start_httpd >&3 2>&4 -then - say "skipping test, web server setup failed" - test_done - exit -fi +start_httpd test_expect_success 'setup remote repository' ' cd "$ROOT_PATH" && @@ -73,6 +67,42 @@ test_expect_success ' push to remote repository with unpacked refs' ' test $HEAD = $(git rev-parse --verify HEAD)) ' +test_expect_success 'http-push fetches unpacked objects' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ + "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_unpacked.git && + + git clone $HTTPD_URL/test_repo_unpacked.git \ + "$ROOT_PATH"/fetch_unpacked && + + # By reset, we force git to retrieve the object + (cd "$ROOT_PATH"/fetch_unpacked && + git reset --hard HEAD^ && + git remote rm origin && + git reflog expire --expire=0 --all && + git prune && + git push -f -v $HTTPD_URL/test_repo_unpacked.git master) +' + +test_expect_success 'http-push fetches packed objects' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ + "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && + + git clone $HTTPD_URL/test_repo_packed.git \ + "$ROOT_PATH"/test_repo_clone_packed && + + (cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo_packed.git && + git --bare repack && + git --bare prune-packed) && + + # By reset, we force git to retrieve the packed object + (cd "$ROOT_PATH"/test_repo_clone_packed && + git reset --hard HEAD^ && + git remote rm origin && + git reflog expire --expire=0 --all && + git prune && + git push -f -v $HTTPD_URL/test_repo_packed.git master) +' + test_expect_success 'create and delete remote branch' ' cd "$ROOT_PATH"/test_repo_clone && git checkout -b dev && diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh new file mode 100755 index 0000000000..0e69324652 --- /dev/null +++ b/t/t5550-http-fetch.sh @@ -0,0 +1,65 @@ +#!/bin/sh + +test_description='test fetching over http' +. ./test-lib.sh + +if test -n "$NO_CURL"; then + say 'skipping test, git built without http support' + test_done +fi + +. "$TEST_DIRECTORY"/lib-httpd.sh +LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'} +start_httpd + +test_expect_success 'setup repository' ' + echo content >file && + git add file && + git commit -m one +' + +test_expect_success 'create http-accessible bare repository' ' + mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --bare init && + echo "exec git update-server-info" >hooks/post-update && + chmod +x hooks/post-update + ) && + git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git push public master:master +' + +test_expect_success 'clone http repository' ' + git clone $HTTPD_URL/repo.git clone && + test_cmp file clone/file +' + +test_expect_success 'fetch changes via http' ' + echo content >>file && + git commit -a -m two && + git push public + (cd clone && git pull) && + test_cmp file clone/file +' + +test_expect_success 'http remote detects correct HEAD' ' + git push public master:other && + (cd clone && + git remote set-head origin -d && + git remote set-head origin -a && + git symbolic-ref refs/remotes/origin/HEAD > output && + echo refs/remotes/origin/master > expect && + test_cmp expect output + ) +' + +test_expect_success 'fetch packed objects' ' + cp -R "$HTTPD_DOCUMENT_ROOT_PATH"/repo.git "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && + cd "$HTTPD_DOCUMENT_ROOT_PATH"/repo_pack.git && + git --bare repack && + git --bare prune-packed && + git clone $HTTPD_URL/repo_pack.git +' + +stop_httpd +test_done diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 44793f2eee..2335d8bc85 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -159,4 +159,19 @@ test_expect_success 'clone a void' ' test_cmp target-6/.git/config target-7/.git/config ' +test_expect_success 'clone respects global branch.autosetuprebase' ' + ( + HOME=$(pwd) && + export HOME && + test_config="$HOME/.gitconfig" && + unset GIT_CONFIG_NOGLOBAL && + git config -f "$test_config" branch.autosetuprebase remote && + rm -fr dst && + git clone src dst && + cd dst && + actual="z$(git config branch.master.rebase)" && + test ztrue = $actual + ) +' + test_done diff --git a/t/t5602-clone-remote-exec.sh b/t/t5602-clone-remote-exec.sh index 82b1d1e2b3..deffdaee49 100755 --- a/t/t5602-clone-remote-exec.sh +++ b/t/t5602-clone-remote-exec.sh @@ -18,8 +18,8 @@ test_expect_success 'clone calls git upload-pack unqualified with no -u option' ' test_expect_success 'clone calls specified git upload-pack with -u option' ' - GIT_SSH=./not_ssh git clone -u /something/bin/git-upload-pack localhost:/path/to/repo junk - echo "localhost /something/bin/git-upload-pack '\''/path/to/repo'\''" >expected + GIT_SSH=./not_ssh git clone -u ./something/bin/git-upload-pack localhost:/path/to/repo junk + echo "localhost ./something/bin/git-upload-pack '\''/path/to/repo'\''" >expected test_cmp expected not_ssh_output ' diff --git a/t/t5701-clone-local.sh b/t/t5701-clone-local.sh index 3559d17964..19b5c0d552 100755 --- a/t/t5701-clone-local.sh +++ b/t/t5701-clone-local.sh @@ -132,4 +132,14 @@ test_expect_success 'clone empty repository' ' test $actual = $expected) ' +test_expect_success 'clone empty repository, and then push should not segfault.' ' + cd "$D" && + rm -fr empty/ empty-clone/ && + mkdir empty && + (cd empty && git init) && + git clone empty empty-clone && + (cd empty-clone && + test_must_fail git push) +' + test_done diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh new file mode 100755 index 0000000000..9f52154cac --- /dev/null +++ b/t/t5705-clone-2gb.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='Test cloning a repository larger than 2 gigabyte' +. ./test-lib.sh + +test -z "$GIT_TEST_CLONE_2GB" && +say "Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" && +test_done && +exit + +test_expect_success 'setup' ' + + git config pack.compression 0 && + git config pack.depth 0 && + blobsize=$((20*1024*1024)) && + blobcount=$((2*1024*1024*1024/$blobsize+1)) && + i=1 && + (while test $i -le $blobcount + do + printf "Generating blob $i/$blobcount\r" >&2 && + printf "blob\nmark :$i\ndata $blobsize\n" && + #test-genrandom $i $blobsize && + printf "%-${blobsize}s" $i && + echo "M 100644 :$i $i" >> commit + i=$(($i+1)) || + echo $? > exit-status + done && + echo "commit refs/heads/master" && + echo "author A U Thor <author@email.com> 123456789 +0000" && + echo "committer C O Mitter <committer@email.com> 123456789 +0000" && + echo "data 5" && + echo ">2gb" && + cat commit) | + git fast-import && + test ! -f exit-status + +' + +test_expect_success 'clone' ' + + git clone --bare --no-hardlinks . clone + +' + +test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index f8942bc890..7dcf391914 100755 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -54,6 +54,12 @@ deduxit me super semitas jusitiae, EOF printf "propter nomen suum." >> new4.txt +test_expect_success 'merge with no changes' ' + cp orig.txt test.txt && + git merge-file test.txt orig.txt orig.txt && + test_cmp test.txt orig.txt +' + cp new1.txt test.txt test_expect_success "merge without conflict" \ "git merge-file test.txt orig.txt new2.txt" diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 052a6c90f5..4556cdd8d2 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -482,28 +482,89 @@ test_expect_success 'good merge bases when good and bad are siblings' ' git bisect reset ' -check_trace() { - grep "$1" "$GIT_TRACE" | grep "\^$2" | grep "$3" >/dev/null -} - test_expect_success 'optimized merge base checks' ' - GIT_TRACE="$(pwd)/trace.log" && - export GIT_TRACE && git bisect start "$HASH7" "$SIDE_HASH7" > my_bisect_log.txt && grep "merge base must be tested" my_bisect_log.txt && grep "$HASH4" my_bisect_log.txt && - check_trace "rev-list" "$HASH7" "$SIDE_HASH7" && git bisect good > my_bisect_log2.txt && test -f ".git/BISECT_ANCESTORS_OK" && test "$HASH6" = $(git rev-parse --verify HEAD) && - : > "$GIT_TRACE" && git bisect bad > my_bisect_log3.txt && - test_must_fail check_trace "rev-list" "$HASH6" "$SIDE_HASH7" && git bisect good "$A_HASH" > my_bisect_log4.txt && grep "merge base must be tested" my_bisect_log4.txt && - test_must_fail test -f ".git/BISECT_ANCESTORS_OK" && - check_trace "rev-list" "$HASH6" "$A_HASH" && - unset GIT_TRACE + test_must_fail test -f ".git/BISECT_ANCESTORS_OK" +' + +# This creates another side branch called "parallel" with some files +# in some directories, to test bisecting with paths. +# +# We should have the following: +# +# P1-P2-P3-P4-P5-P6-P7 +# / / / +# H1-H2-H3-H4-H5-H6-H7 +# \ \ \ +# S5-A \ +# \ \ +# S6-S7----B +# +test_expect_success '"parallel" side branch creation' ' + git bisect reset && + git checkout -b parallel $HASH1 && + mkdir dir1 dir2 && + add_line_into_file "1(para): line 1 on parallel branch" dir1/file1 && + PARA_HASH1=$(git rev-parse --verify HEAD) && + add_line_into_file "2(para): line 2 on parallel branch" dir2/file2 && + PARA_HASH2=$(git rev-parse --verify HEAD) && + add_line_into_file "3(para): line 3 on parallel branch" dir2/file3 && + PARA_HASH3=$(git rev-parse --verify HEAD) + git merge -m "merge HASH4 and PARA_HASH3" "$HASH4" && + PARA_HASH4=$(git rev-parse --verify HEAD) + add_line_into_file "5(para): add line on parallel branch" dir1/file1 && + PARA_HASH5=$(git rev-parse --verify HEAD) + add_line_into_file "6(para): add line on parallel branch" dir2/file2 && + PARA_HASH6=$(git rev-parse --verify HEAD) + git merge -m "merge HASH7 and PARA_HASH6" "$HASH7" && + PARA_HASH7=$(git rev-parse --verify HEAD) +' + +test_expect_success 'restricting bisection on one dir' ' + git bisect reset && + git bisect start HEAD $HASH1 -- dir1 && + para1=$(git rev-parse --verify HEAD) && + test "$para1" = "$PARA_HASH1" && + git bisect bad > my_bisect_log.txt && + grep "$PARA_HASH1 is first bad commit" my_bisect_log.txt +' + +test_expect_success 'restricting bisection on one dir and a file' ' + git bisect reset && + git bisect start HEAD $HASH1 -- dir1 hello && + para4=$(git rev-parse --verify HEAD) && + test "$para4" = "$PARA_HASH4" && + git bisect bad && + hash3=$(git rev-parse --verify HEAD) && + test "$hash3" = "$HASH3" && + git bisect good && + hash4=$(git rev-parse --verify HEAD) && + test "$hash4" = "$HASH4" && + git bisect good && + para1=$(git rev-parse --verify HEAD) && + test "$para1" = "$PARA_HASH1" && + git bisect good > my_bisect_log.txt && + grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt +' + +test_expect_success 'skipping away from skipped commit' ' + git bisect start $PARA_HASH7 $HASH1 && + para4=$(git rev-parse --verify HEAD) && + test "$para4" = "$PARA_HASH4" && + git bisect skip && + hash7=$(git rev-parse --verify HEAD) && + test "$hash7" = "$HASH7" && + git bisect skip && + hash3=$(git rev-parse --verify HEAD) && + test "$hash3" = "$HASH3" ' # diff --git a/t/t6031-merge-recursive.sh b/t/t6031-merge-recursive.sh index 8073e0c3ef..8a3304fb0b 100755 --- a/t/t6031-merge-recursive.sh +++ b/t/t6031-merge-recursive.sh @@ -3,8 +3,10 @@ test_description='merge-recursive: handle file mode' . ./test-lib.sh -# Note that we follow "chmod +x F" with "update-index --chmod=+x F" to -# help filesystems that do not have the executable bit. +if ! test "$(git config --bool core.filemode)" = false +then + test_set_prereq FILEMODE +fi test_expect_success 'mode change in one branch: keep changed version' ' : >file1 && @@ -15,11 +17,14 @@ test_expect_success 'mode change in one branch: keep changed version' ' git add dummy && git commit -m a && git checkout -b b1 master && - chmod +x file1 && - git update-index --chmod=+x file1 && + test_chmod +x file1 && git commit -m b1 && git checkout a1 && git merge-recursive master -- a1 b1 && + git ls-files -s file1 | grep ^100755 +' + +test_expect_success FILEMODE 'verify executable bit on file' ' test -x file1 ' @@ -28,8 +33,7 @@ test_expect_success 'mode change in both branches: expect conflict' ' git checkout -b a2 master && : >file2 && H=$(git hash-object file2) && - chmod +x file2 && - git update-index --add --chmod=+x file2 && + test_chmod +x file2 && git commit -m a2 && git checkout -b b2 master && : >file2 && @@ -46,6 +50,10 @@ test_expect_success 'mode change in both branches: expect conflict' ' echo "100644 $H 3 file2" ) >expect && test_cmp actual expect && + git ls-files -s file2 | grep ^100755 +' + +test_expect_success FILEMODE 'verify executable bit on file' ' test -x file2 ' diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6034-merge-rename-nocruft.sh index 65be95fbaa..65be95fbaa 100755 --- a/t/t6023-merge-rename-nocruft.sh +++ b/t/t6034-merge-rename-nocruft.sh diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index ba9060190d..00e1de9627 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -29,7 +29,9 @@ test_expect_success setup ' git checkout -b b4 origin && advance e && advance f - ) + ) && + git checkout -b follower --track master && + advance g ' script='s/^..\(b.\)[ 0-9a-f]*\[\([^]]*\)\].*/\1 \2/p' @@ -56,6 +58,12 @@ test_expect_success 'checkout' ' grep "have 1 and 1 different" actual ' +test_expect_success 'checkout with local tracked branch' ' + git checkout master && + git checkout follower >actual + grep "is ahead of" actual +' + test_expect_success 'status' ' ( cd test && @@ -66,5 +74,19 @@ test_expect_success 'status' ' grep "have 1 and 1 different" actual ' +test_expect_success 'status when tracking lightweight tags' ' + git checkout master && + git tag light && + git branch --track lighttrack light >actual && + grep "set up to track" actual && + git checkout lighttrack +' +test_expect_success 'status when tracking annotated tags' ' + git checkout master && + git tag -m heavy heavy && + git branch --track heavytrack heavy >actual && + grep "set up to track" actual && + git checkout heavytrack +' test_done diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index 8f5a06f7dd..42f6fff373 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -83,13 +83,13 @@ test_expect_success 'merge-msg test #1' ' ' cat >expected <<EOF -Merge branch 'left' of $TEST_DIRECTORY/$test +Merge branch 'left' of $(pwd) EOF test_expect_success 'merge-msg test #2' ' git checkout master && - git fetch "$TEST_DIRECTORY/$test" left && + git fetch "$(pwd)" left && git fmt-merge-msg <.git/FETCH_HEAD >actual && test_cmp expected actual @@ -208,4 +208,36 @@ test_expect_success 'merge-msg test #5-2' ' test_cmp expected actual ' +test_expect_success 'merge-msg -F' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.summary yes && + + git checkout master && + setdate && + git fetch . left right && + + git fmt-merge-msg -F .git/FETCH_HEAD >actual && + test_cmp expected actual +' + +test_expect_success 'merge-msg -F in subdirectory' ' + + git config --unset-all merge.log + git config --unset-all merge.summary + git config merge.summary yes && + + git checkout master && + setdate && + git fetch . left right && + mkdir sub && + cp .git/FETCH_HEAD sub/FETCH_HEAD && + ( + cd sub && + git fmt-merge-msg -F FETCH_HEAD >../actual + ) && + test_cmp expected actual +' + test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 8bfae44a83..8052c86ad3 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -26,6 +26,13 @@ test_expect_success 'Create sample commit with known timestamp' ' git tag -a -m "Tagging at $datestamp" testtag ' +test_expect_success 'Create upstream config' ' + git update-ref refs/remotes/origin/master master && + git remote add origin nowhere && + git config branch.master.remote origin && + git config branch.master.merge refs/heads/master +' + test_atom() { case "$1" in head) ref=refs/heads/master ;; @@ -39,6 +46,7 @@ test_atom() { } test_atom head refname refs/heads/master +test_atom head upstream refs/remotes/origin/master test_atom head objecttype commit test_atom head objectsize 171 test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581 @@ -68,6 +76,7 @@ test_atom head contents 'Initial ' test_atom tag refname refs/tags/testtag +test_atom tag upstream '' test_atom tag objecttype tag test_atom tag objectsize 154 test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322 @@ -203,6 +212,7 @@ test_expect_success 'Check format "rfc2822" date fields output' ' cat >expected <<\EOF refs/heads/master +refs/remotes/origin/master refs/tags/testtag EOF @@ -214,6 +224,7 @@ test_expect_success 'Verify ascending sort' ' cat >expected <<\EOF refs/tags/testtag +refs/remotes/origin/master refs/heads/master EOF @@ -224,6 +235,7 @@ test_expect_success 'Verify descending sort' ' cat >expected <<\EOF 'refs/heads/master' +'refs/remotes/origin/master' 'refs/tags/testtag' EOF @@ -244,6 +256,7 @@ test_expect_success 'Quoting style: python' ' cat >expected <<\EOF "refs/heads/master" +"refs/remotes/origin/master" "refs/tags/testtag" EOF @@ -273,16 +286,26 @@ test_expect_success 'Check short refname format' ' test_cmp expected actual ' +cat >expected <<EOF +origin/master +EOF + +test_expect_success 'Check short upstream format' ' + git for-each-ref --format="%(upstream:short)" refs/heads >actual && + test_cmp expected actual +' + test_expect_success 'Check for invalid refname format' ' test_must_fail git for-each-ref --format="%(refname:INVALID)" ' cat >expected <<\EOF heads/master -master +tags/master EOF -test_expect_success 'Check ambiguous head and tag refs' ' +test_expect_success 'Check ambiguous head and tag refs (strict)' ' + git config --bool core.warnambiguousrefs true && git checkout -b newtag && echo "Using $datestamp" > one && git add one && @@ -294,11 +317,22 @@ test_expect_success 'Check ambiguous head and tag refs' ' ' cat >expected <<\EOF +heads/master +master +EOF + +test_expect_success 'Check ambiguous head and tag refs (loose)' ' + git config --bool core.warnambiguousrefs false && + git for-each-ref --format "%(refname:short)" refs/heads/master refs/tags/master >actual && + test_cmp expected actual +' + +cat >expected <<\EOF heads/ambiguous ambiguous EOF -test_expect_success 'Check ambiguous head and tag refs II' ' +test_expect_success 'Check ambiguous head and tag refs II (loose)' ' git checkout master && git tag ambiguous testtag^0 && git branch ambiguous testtag^0 && diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 8fb3a56838..10b8f8c44b 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -206,7 +206,7 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' ' rm -f dirty dirty2 -test_expect_success 'git mv should overwrite symlink to a file' ' +test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' ' rm -fr .git && git init && @@ -225,7 +225,7 @@ test_expect_success 'git mv should overwrite symlink to a file' ' rm -f moved symlink -test_expect_success 'git mv should overwrite file with a symlink' ' +test_expect_success SYMLINKS 'git mv should overwrite file with a symlink' ' rm -fr .git && git init && diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index b81593780a..f275af8240 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -16,12 +16,13 @@ test_expect_success setup ' echo foo mmap bar_mmap echo foo_mmap bar mmap baz } >file && + echo ww w >w && echo x x xx x >x && echo y yy >y && echo zzz > z && mkdir t && echo test >t/t && - git add file x y z t/t && + git add file w x y z t/t && test_tick && git commit -m initial ' @@ -48,6 +49,12 @@ do diff expected actual ' + test_expect_success "grep -w $L (w)" ' + : >expected && + ! git grep -n -w -e "^w" >actual && + test_cmp expected actual + ' + test_expect_success "grep -w $L (x)" ' { echo ${HC}x:1:x x xx x diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 69501e2711..73dbc4360b 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -185,8 +185,9 @@ cba EOF test_expect_success \ 'listing tags with substring as pattern must print those matching' ' - git tag -l "*a*" > actual && - test_cmp expect actual + rm *a* && + git tag -l "*a*" > current && + test_cmp expect current ' cat >expect <<EOF @@ -580,28 +581,38 @@ test_expect_success \ ' # subsequent tests require gpg; check if it is available -gpg --version >/dev/null +gpg --version >/dev/null 2>/dev/null if [ $? -eq 127 ]; then - echo "gpg not found - skipping tag signing and verification tests" - test_done - exit + say "gpg not found - skipping tag signing and verification tests" +else + # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 + # the gpg version 1.0.6 didn't parse trust packets correctly, so for + # that version, creation of signed tags using the generated key fails. + case "$(gpg --version)" in + 'gpg (GnuPG) 1.0.6'*) + say "Skipping signed tag tests, because a bug in 1.0.6 version" + ;; + *) + test_set_prereq GPG + ;; + esac fi # trying to verify annotated non-signed tags: -test_expect_success \ +test_expect_success GPG \ 'trying to verify an annotated non-signed tag should fail' ' tag_exists annotated-tag && test_must_fail git tag -v annotated-tag ' -test_expect_success \ +test_expect_success GPG \ 'trying to verify a file-annotated non-signed tag should fail' ' tag_exists file-annotated-tag && test_must_fail git tag -v file-annotated-tag ' -test_expect_success \ +test_expect_success GPG \ 'trying to verify two annotated non-signed tags should fail' ' tag_exists annotated-tag file-annotated-tag && test_must_fail git tag -v annotated-tag file-annotated-tag @@ -609,17 +620,6 @@ test_expect_success \ # creating and verifying signed tags: -# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 -# the gpg version 1.0.6 didn't parse trust packets correctly, so for -# that version, creation of signed tags using the generated key fails. -case "$(gpg --version)" in -'gpg (GnuPG) 1.0.6'*) - echo "Skipping signed tag tests, because a bug in 1.0.6 version" - test_done - exit - ;; -esac - # key generation info: gpg --homedir t/t7004 --gen-key # Type DSA and Elgamal, size 2048 bits, no expiration date. # Name and email: C O Mitter <committer@example.com> @@ -633,7 +633,7 @@ export GNUPGHOME get_tag_header signed-tag $commit commit $time >expect echo 'A signed tag message' >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success 'creating a signed tag with -m message should succeed' ' +test_expect_success GPG 'creating a signed tag with -m message should succeed' ' git tag -s -m "A signed tag message" signed-tag && get_tag_msg signed-tag >actual && test_cmp expect actual @@ -642,7 +642,7 @@ test_expect_success 'creating a signed tag with -m message should succeed' ' get_tag_header u-signed-tag $commit commit $time >expect echo 'Another message' >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success 'sign with a given key id' ' +test_expect_success GPG 'sign with a given key id' ' git tag -u committer@example.com -m "Another message" u-signed-tag && get_tag_msg u-signed-tag >actual && @@ -650,14 +650,14 @@ test_expect_success 'sign with a given key id' ' ' -test_expect_success 'sign with an unknown id (1)' ' +test_expect_success GPG 'sign with an unknown id (1)' ' test_must_fail git tag -u author@example.com \ -m "Another message" o-signed-tag ' -test_expect_success 'sign with an unknown id (2)' ' +test_expect_success GPG 'sign with an unknown id (2)' ' test_must_fail git tag -u DEADBEEF -m "Another message" o-signed-tag @@ -674,7 +674,7 @@ chmod +x fakeeditor get_tag_header implied-sign $commit commit $time >expect ./fakeeditor >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success '-u implies signed tag' ' +test_expect_success GPG '-u implies signed tag' ' GIT_EDITOR=./fakeeditor git tag -u CDDE430D implied-sign && get_tag_msg implied-sign >actual && test_cmp expect actual @@ -687,7 +687,7 @@ EOF get_tag_header file-signed-tag $commit commit $time >expect cat sigmsgfile >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with -F messagefile should succeed' ' git tag -s -F sigmsgfile file-signed-tag && get_tag_msg file-signed-tag >actual && @@ -701,7 +701,7 @@ EOF get_tag_header stdin-signed-tag $commit commit $time >expect cat siginputmsg >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success 'creating a signed tag with -F - should succeed' ' +test_expect_success GPG 'creating a signed tag with -F - should succeed' ' git tag -s -F - stdin-signed-tag <siginputmsg && get_tag_msg stdin-signed-tag >actual && test_cmp expect actual @@ -710,13 +710,13 @@ test_expect_success 'creating a signed tag with -F - should succeed' ' get_tag_header implied-annotate $commit commit $time >expect ./fakeeditor >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success '-s implies annotated tag' ' +test_expect_success GPG '-s implies annotated tag' ' GIT_EDITOR=./fakeeditor git tag -s implied-annotate && get_tag_msg implied-annotate >actual && test_cmp expect actual ' -test_expect_success \ +test_expect_success GPG \ 'trying to create a signed tag with non-existing -F file should fail' ' ! test -f nonexistingfile && ! tag_exists nosigtag && @@ -724,13 +724,13 @@ test_expect_success \ ! tag_exists nosigtag ' -test_expect_success 'verifying a signed tag should succeed' \ +test_expect_success GPG 'verifying a signed tag should succeed' \ 'git tag -v signed-tag' -test_expect_success 'verifying two signed tags in one command should succeed' \ +test_expect_success GPG 'verifying two signed tags in one command should succeed' \ 'git tag -v signed-tag file-signed-tag' -test_expect_success \ +test_expect_success GPG \ 'verifying many signed and non-signed tags should fail' ' test_must_fail git tag -v signed-tag annotated-tag && test_must_fail git tag -v file-annotated-tag file-signed-tag && @@ -739,7 +739,7 @@ test_expect_success \ test_must_fail git tag -v signed-tag annotated-tag file-signed-tag ' -test_expect_success 'verifying a forged tag should fail' ' +test_expect_success GPG 'verifying a forged tag should fail' ' forged=$(git cat-file tag signed-tag | sed -e "s/signed-tag/forged-tag/" | git mktag) && @@ -751,7 +751,7 @@ test_expect_success 'verifying a forged tag should fail' ' get_tag_header empty-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with an empty -m message should succeed' ' git tag -s -m "" empty-signed-tag && get_tag_msg empty-signed-tag >actual && @@ -762,7 +762,7 @@ test_expect_success \ >sigemptyfile get_tag_header emptyfile-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with an empty -F messagefile should succeed' ' git tag -s -F sigemptyfile emptyfile-signed-tag && get_tag_msg emptyfile-signed-tag >actual && @@ -785,7 +785,7 @@ Trailing spaces Trailing blank lines EOF echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'extra blanks in the message for a signed tag should be removed' ' git tag -s -F sigblanksfile blanks-signed-tag && get_tag_msg blanks-signed-tag >actual && @@ -795,7 +795,7 @@ test_expect_success \ get_tag_header blank-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with a blank -m message should succeed' ' git tag -s -m " " blank-signed-tag && get_tag_msg blank-signed-tag >actual && @@ -808,7 +808,7 @@ echo '' >>sigblankfile echo ' ' >>sigblankfile get_tag_header blankfile-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with blank -F file with spaces should succeed' ' git tag -s -F sigblankfile blankfile-signed-tag && get_tag_msg blankfile-signed-tag >actual && @@ -819,7 +819,7 @@ test_expect_success \ printf ' ' >sigblanknonlfile get_tag_header blanknonlfile-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with spaces and no newline should succeed' ' git tag -s -F sigblanknonlfile blanknonlfile-signed-tag && get_tag_msg blanknonlfile-signed-tag >actual && @@ -856,7 +856,7 @@ Another line. Last line. EOF echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with a -F file with #comments should succeed' ' git tag -s -F sigcommentsfile comments-signed-tag && get_tag_msg comments-signed-tag >actual && @@ -866,7 +866,7 @@ test_expect_success \ get_tag_header comment-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with #commented -m message should succeed' ' git tag -s -m "#comment" comment-signed-tag && get_tag_msg comment-signed-tag >actual && @@ -879,7 +879,7 @@ echo '' >>sigcommentfile echo '####' >>sigcommentfile get_tag_header commentfile-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with #commented -F messagefile should succeed' ' git tag -s -F sigcommentfile commentfile-signed-tag && get_tag_msg commentfile-signed-tag >actual && @@ -890,7 +890,7 @@ test_expect_success \ printf '#comment' >sigcommentnonlfile get_tag_header commentnonlfile-signed-tag $commit commit $time >expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag with a #comment and no newline should succeed' ' git tag -s -F sigcommentnonlfile commentnonlfile-signed-tag && get_tag_msg commentnonlfile-signed-tag >actual && @@ -900,7 +900,7 @@ test_expect_success \ # listing messages for signed tags: -test_expect_success \ +test_expect_success GPG \ 'listing the one-line message of a signed tag should succeed' ' git tag -s -m "A message line signed" stag-one-line && @@ -925,7 +925,7 @@ test_expect_success \ test_cmp expect actual ' -test_expect_success \ +test_expect_success GPG \ 'listing the zero-lines message of a signed tag should succeed' ' git tag -s -m "" stag-zero-lines && @@ -953,7 +953,7 @@ test_expect_success \ echo 'stag line one' >sigtagmsg echo 'stag line two' >>sigtagmsg echo 'stag line three' >>sigtagmsg -test_expect_success \ +test_expect_success GPG \ 'listing many message lines of a signed tag should succeed' ' git tag -s -F sigtagmsg stag-lines && @@ -998,12 +998,12 @@ test_expect_success \ tree=$(git rev-parse HEAD^{tree}) blob=$(git rev-parse HEAD:foo) -tag=$(git rev-parse signed-tag) +tag=$(git rev-parse signed-tag 2>/dev/null) get_tag_header tree-signed-tag $tree tree $time >expect echo "A message for a tree" >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag pointing to a tree should succeed' ' git tag -s -m "A message for a tree" tree-signed-tag HEAD^{tree} && get_tag_msg tree-signed-tag >actual && @@ -1013,7 +1013,7 @@ test_expect_success \ get_tag_header blob-signed-tag $blob blob $time >expect echo "A message for a blob" >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag pointing to a blob should succeed' ' git tag -s -m "A message for a blob" blob-signed-tag HEAD:foo && get_tag_msg blob-signed-tag >actual && @@ -1023,7 +1023,7 @@ test_expect_success \ get_tag_header tag-signed-tag $tag tag $time >expect echo "A message for another tag" >>expect echo '-----BEGIN PGP SIGNATURE-----' >>expect -test_expect_success \ +test_expect_success GPG \ 'creating a signed tag pointing to another tag should succeed' ' git tag -s -m "A message for another tag" tag-signed-tag signed-tag && get_tag_msg tag-signed-tag >actual && @@ -1032,7 +1032,7 @@ test_expect_success \ # try to sign with bad user.signingkey git config user.signingkey BobTheMouse -test_expect_success \ +test_expect_success GPG \ 'git tag -s fails if gpg is misconfigured' \ 'test_must_fail git tag -s -m tail tag-gpg-failure' git config --unset user.signingkey @@ -1040,7 +1040,7 @@ git config --unset user.signingkey # try to verify without gpg: rm -rf gpghome -test_expect_success \ +test_expect_success GPG \ 'verify signed tag fails when public key is not present' \ 'test_must_fail git tag -v signed-tag' diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 2d919d69ef..b647957d75 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -7,6 +7,7 @@ test_description='GIT_EDITOR, core.editor, and stuff' for i in GIT_EDITOR core_editor EDITOR VISUAL vi do cat >e-$i.sh <<-EOF + #!$SHELL_PATH echo "Edited by $i" >"\$1" EOF chmod +x e-$i.sh @@ -87,30 +88,26 @@ do ' done +if ! echo 'echo space > "$1"' > "e space.sh" +then + say "Skipping; FS does not support spaces in filenames" + test_done +fi + test_expect_success 'editor with a space' ' - if echo "echo space > \"\$1\"" > "e space.sh" - then - chmod a+x "e space.sh" && - GIT_EDITOR="./e\ space.sh" git commit --amend && - test space = "$(git show -s --pretty=format:%s)" - else - say "Skipping; FS does not support spaces in filenames" - fi + chmod a+x "e space.sh" && + GIT_EDITOR="./e\ space.sh" git commit --amend && + test space = "$(git show -s --pretty=format:%s)" ' unset GIT_EDITOR test_expect_success 'core.editor with a space' ' - if test -f "e space.sh" - then - git config core.editor \"./e\ space.sh\" && - git commit --amend && - test space = "$(git show -s --pretty=format:%s)" - else - say "Skipping; FS does not support spaces in filenames" - fi + git config core.editor \"./e\ space.sh\" && + git commit --amend && + test space = "$(git show -s --pretty=format:%s)" ' diff --git a/t/t7201-co.sh b/t/t7201-co.sh index bdb808af1a..ebfd34df36 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -534,4 +534,12 @@ test_expect_success 'failing checkout -b should not break working tree' ' ' +test_expect_success 'switch out of non-branch' ' + git reset --hard master && + git checkout master^0 && + echo modified >one && + test_must_fail git checkout renamer 2>error.log && + ! grep "^Previous HEAD" error.log +' + test_done diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 1636fac2a4..929d5d4d3b 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -373,9 +373,9 @@ test_expect_success 'removal failure' ' mkdir foo && touch foo/bar && - exec <foo/bar && - chmod 0 foo && - test_must_fail git clean -f -d + (exec <foo/bar && + chmod 0 foo && + test_must_fail git clean -f -d) ' chmod 755 foo diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index af690ec6c1..0f2ccc6cf0 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -64,6 +64,16 @@ test_expect_success 'submodule add' ' ) ' +test_expect_success 'submodule add --branch' ' + ( + cd addtest && + git submodule add -b initial "$submodurl" submod-branch && + git submodule init && + cd submod-branch && + git branch | grep initial + ) +' + test_expect_success 'submodule add with ./ in path' ' ( cd addtest && diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index aa6c44ce4f..9a21f783d3 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -54,13 +54,13 @@ test_expect_success setup ' git merge -s ours a ' -test_expect_failure 'merging with modify/modify conflict' ' +test_expect_success 'merging with modify/modify conflict' ' git checkout -b test1 a && test_must_fail git merge b && test -f .git/MERGE_MSG && - git diff - + git diff && + test -n "$(git ls-files -u)" ' test_expect_success 'merging with a modify/modify conflict between merge bases' ' diff --git a/t/t7406-submodule-reference.sh b/t/t7406-submodule-reference.sh new file mode 100755 index 0000000000..cc16d3f05d --- /dev/null +++ b/t/t7406-submodule-reference.sh @@ -0,0 +1,81 @@ +#!/bin/sh +# +# Copyright (c) 2009, Red Hat Inc, Author: Michael S. Tsirkin (mst@redhat.com) +# + +test_description='test clone --reference' +. ./test-lib.sh + +base_dir=`pwd` + +U=$base_dir/UPLOAD_LOG + +test_expect_success 'preparing first repository' \ +'test_create_repo A && cd A && +echo first > file1 && +git add file1 && +git commit -m A-initial' + +cd "$base_dir" + +test_expect_success 'preparing second repository' \ +'git clone A B && cd B && +echo second > file2 && +git add file2 && +git commit -m B-addition && +git repack -a -d && +git prune' + +cd "$base_dir" + +test_expect_success 'preparing supermodule' \ +'test_create_repo super && cd super && +echo file > file && +git add file && +git commit -m B-super-initial' + +cd "$base_dir" + +test_expect_success 'submodule add --reference' \ +'cd super && git submodule add --reference ../B "file://$base_dir/A" sub && +git commit -m B-super-added' + +cd "$base_dir" + +test_expect_success 'after add: existence of info/alternates' \ +'test `wc -l <super/sub/.git/objects/info/alternates` = 1' + +cd "$base_dir" + +test_expect_success 'that reference gets used with add' \ +'cd super/sub && +echo "0 objects, 0 kilobytes" > expected && +git count-objects > current && +diff expected current' + +cd "$base_dir" + +test_expect_success 'cloning supermodule' \ +'git clone super super-clone' + +cd "$base_dir" + +test_expect_success 'update with reference' \ +'cd super-clone && git submodule update --init --reference ../B' + +cd "$base_dir" + +test_expect_success 'after update: existence of info/alternates' \ +'test `wc -l <super-clone/sub/.git/objects/info/alternates` = 1' + +cd "$base_dir" + +test_expect_success 'that reference gets used with update' \ +'cd super-clone/sub && +echo "0 objects, 0 kilobytes" > expected && +git count-objects > current && +diff expected current' + +cd "$base_dir" + +test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh new file mode 100755 index 0000000000..0773fe405b --- /dev/null +++ b/t/t7406-submodule-update.sh @@ -0,0 +1,140 @@ +#!/bin/sh +# +# Copyright (c) 2009 Red Hat, Inc. +# + +test_description='Test updating submodules + +This test verifies that "git submodule update" detaches the HEAD of the +submodule and "git submodule update --rebase" does not detach the HEAD. +' + +. ./test-lib.sh + + +compare_head() +{ + sha_master=`git-rev-list --max-count=1 master` + sha_head=`git-rev-list --max-count=1 HEAD` + + test "$sha_master" = "$sha_head" +} + + +test_expect_success 'setup a submodule tree' ' + echo file > file && + git add file && + test_tick && + git commit -m upstream + git clone . super && + git clone super submodule && + (cd super && + git submodule add ../submodule submodule && + test_tick && + git commit -m "submodule" && + git submodule init submodule + ) && + (cd submodule && + echo "line2" > file && + git add file && + git commit -m "Commit 2" + ) && + (cd super && + (cd submodule && + git pull --rebase origin + ) && + git add submodule && + git commit -m "submodule update" + ) +' + +test_expect_success 'submodule update detaching the HEAD ' ' + (cd super/submodule && + git reset --hard HEAD~1 + ) && + (cd super && + (cd submodule && + compare_head + ) && + git submodule update submodule && + cd submodule && + ! compare_head + ) +' + +test_expect_success 'submodule update --rebase staying on master' ' + (cd super/submodule && + git checkout master + ) && + (cd super && + (cd submodule && + compare_head + ) && + git submodule update --rebase submodule && + cd submodule && + compare_head + ) +' + +test_expect_success 'submodule update - rebase in .git/config' ' + (cd super && + git config submodule.submodule.update rebase + ) && + (cd super/submodule && + git reset --hard HEAD~1 + ) && + (cd super && + (cd submodule && + compare_head + ) && + git submodule update submodule && + cd submodule && + compare_head + ) +' + +test_expect_success 'submodule update - checkout in .git/config but --rebase given' ' + (cd super && + git config submodule.submodule.update checkout + ) && + (cd super/submodule && + git reset --hard HEAD~1 + ) && + (cd super && + (cd submodule && + compare_head + ) && + git submodule update --rebase submodule && + cd submodule && + compare_head + ) +' + +test_expect_success 'submodule update - checkout in .git/config' ' + (cd super && + git config submodule.submodule.update checkout + ) && + (cd super/submodule && + git reset --hard HEAD^ + ) && + (cd super && + (cd submodule && + compare_head + ) && + git submodule update submodule && + cd submodule && + ! compare_head + ) +' + +test_expect_success 'submodule init picks up rebase' ' + (cd super && + git config submodule.rebasing.url git://non-existing/git && + git config submodule.rebasing.path does-not-matter && + git config submodule.rebasing.update rebase && + git submodule init rebasing && + test "rebase" = $(git config submodule.rebasing.update) + ) +' + +test_done diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh index 5998baf27b..8eec0fa9bc 100755 --- a/t/t7500-commit.sh +++ b/t/t7500-commit.sh @@ -183,4 +183,14 @@ test_expect_success 'commit message from stdin' ' commit_msg_is "Log with foo word" ' +test_expect_success 'commit -F overrides -t' ' + ( + cd subdir && + echo "-F log" > f.log && + echo "-t template" > t.template && + git commit --allow-empty -F f.log -t t.template + ) && + commit_msg_is "-F log" +' + test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index b4e2b4db84..e2ef53228e 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -38,7 +38,7 @@ test_expect_success \ "echo King of the bongo >file && test_must_fail git commit -m foo -a file" -test_expect_success \ +test_expect_success PERL \ "using paths with --interactive" \ "echo bong-o-bong >file && ! (echo 7 | git commit -m foo --interactive file)" @@ -119,7 +119,7 @@ test_expect_success \ "echo 'gak' >file && \ git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a" -test_expect_success \ +test_expect_success PERL \ "interactive add" \ "echo 7 | git commit --interactive | grep 'What now'" diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index ad42c78d7c..56cd866019 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -234,7 +234,7 @@ cat >.git/FAKE_EDITOR <<EOF # kill -TERM command added below. EOF -test_expect_success 'a SIGTERM should break locks' ' +test_expect_success EXECKEEPSPID 'a SIGTERM should break locks' ' echo >>negative && ! "$SHELL_PATH" -c '\'' echo kill -TERM $$ >> .git/FAKE_EDITOR diff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh index b069095995..8528f64c8d 100755 --- a/t/t7503-pre-commit-hook.sh +++ b/t/t7503-pre-commit-hook.sh @@ -69,7 +69,7 @@ test_expect_success '--no-verify with failing hook' ' ' chmod -x "$HOOK" -test_expect_success 'with non-executable hook' ' +test_expect_success POSIXPERM 'with non-executable hook' ' echo "content" >> file && git add file && @@ -77,7 +77,7 @@ test_expect_success 'with non-executable hook' ' ' -test_expect_success '--no-verify with non-executable hook' ' +test_expect_success POSIXPERM '--no-verify with non-executable hook' ' echo "more content" >> file && git add file && diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 47680e6df4..1f53ea8090 100755 --- a/t/t7504-commit-msg-hook.sh +++ b/t/t7504-commit-msg-hook.sh @@ -136,7 +136,7 @@ test_expect_success '--no-verify with failing hook (editor)' ' ' chmod -x "$HOOK" -test_expect_success 'with non-executable hook' ' +test_expect_success POSIXPERM 'with non-executable hook' ' echo "content" >> file && git add file && @@ -144,7 +144,7 @@ test_expect_success 'with non-executable hook' ' ' -test_expect_success 'with non-executable hook (editor)' ' +test_expect_success POSIXPERM 'with non-executable hook (editor)' ' echo "content again" >> file && git add file && @@ -153,7 +153,7 @@ test_expect_success 'with non-executable hook (editor)' ' ' -test_expect_success '--no-verify with non-executable hook' ' +test_expect_success POSIXPERM '--no-verify with non-executable hook' ' echo "more content" >> file && git add file && @@ -161,7 +161,7 @@ test_expect_success '--no-verify with non-executable hook' ' ' -test_expect_success '--no-verify with non-executable hook (editor)' ' +test_expect_success POSIXPERM '--no-verify with non-executable hook (editor)' ' echo "even more content" >> file && git add file && diff --git a/t/t7502-status.sh b/t/t7508-status.sh index 93f875f500..93f875f500 100755 --- a/t/t7502-status.sh +++ b/t/t7508-status.sh diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 6b29bff782..87c9b0e121 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -69,7 +69,7 @@ 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' ' +test_expect_success 'packed obs in alt ODB are repacked when local repo has packs' ' rm -f .git/objects/pack/* && echo new_content >> file1 && git add file1 && diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh new file mode 100755 index 0000000000..ebdccf9a1e --- /dev/null +++ b/t/t7800-difftool.sh @@ -0,0 +1,216 @@ +#!/bin/sh +# +# Copyright (c) 2009 David Aguilar +# + +test_description='git-difftool + +Testing basic diff tool invocation +' + +. ./test-lib.sh + +if ! test_have_prereq PERL; then + say 'skipping difftool tests, perl not available' + test_done +fi + +remove_config_vars() +{ + # Unset all config variables used by git-difftool + git config --unset diff.tool + git config --unset difftool.test-tool.cmd + git config --unset difftool.prompt + git config --unset merge.tool + git config --unset mergetool.test-tool.cmd + return 0 +} + +restore_test_defaults() +{ + # Restores the test defaults used by several tests + remove_config_vars + unset GIT_DIFF_TOOL + unset GIT_MERGE_TOOL + unset GIT_DIFFTOOL_PROMPT + unset GIT_DIFFTOOL_NO_PROMPT + git config diff.tool test-tool && + git config difftool.test-tool.cmd 'cat $LOCAL' +} + +prompt_given() +{ + prompt="$1" + test "$prompt" = "Hit return to launch 'test-tool': branch" +} + +# Create a file on master and change it on branch +test_expect_success 'setup' ' + echo master >file && + git add file && + git commit -m "added file" && + + git checkout -b branch master && + echo branch >file && + git commit -a -m "branch changed file" && + git checkout master +' + +# Configure a custom difftool.<tool>.cmd and use it +test_expect_success 'custom commands' ' + restore_test_defaults && + git config difftool.test-tool.cmd "cat \$REMOTE" && + + diff=$(git difftool --no-prompt branch) && + test "$diff" = "master" && + + restore_test_defaults && + diff=$(git difftool --no-prompt branch) && + test "$diff" = "branch" +' + +# Ensures that git-difftool ignores bogus --tool values +test_expect_success 'difftool ignores bad --tool values' ' + diff=$(git difftool --no-prompt --tool=bogus-tool branch) + test "$?" = 1 && + test "$diff" = "" +' + +# Specify the diff tool using $GIT_DIFF_TOOL +test_expect_success 'GIT_DIFF_TOOL variable' ' + git config --unset diff.tool + GIT_DIFF_TOOL=test-tool && + export GIT_DIFF_TOOL && + + diff=$(git difftool --no-prompt branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + +# Test the $GIT_*_TOOL variables and ensure +# that $GIT_DIFF_TOOL always wins unless --tool is specified +test_expect_success 'GIT_DIFF_TOOL overrides' ' + git config diff.tool bogus-tool && + git config merge.tool bogus-tool && + + GIT_MERGE_TOOL=test-tool && + export GIT_MERGE_TOOL && + diff=$(git difftool --no-prompt branch) && + test "$diff" = "branch" && + unset GIT_MERGE_TOOL && + + GIT_MERGE_TOOL=bogus-tool && + GIT_DIFF_TOOL=test-tool && + export GIT_MERGE_TOOL && + export GIT_DIFF_TOOL && + + diff=$(git difftool --no-prompt branch) && + test "$diff" = "branch" && + + GIT_DIFF_TOOL=bogus-tool && + export GIT_DIFF_TOOL && + + diff=$(git difftool --no-prompt --tool=test-tool branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + +# Test that we don't have to pass --no-prompt to difftool +# when $GIT_DIFFTOOL_NO_PROMPT is true +test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' ' + GIT_DIFFTOOL_NO_PROMPT=true && + export GIT_DIFFTOOL_NO_PROMPT && + + diff=$(git difftool branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + +# git-difftool supports the difftool.prompt variable. +# Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false +test_expect_success 'GIT_DIFFTOOL_PROMPT variable' ' + git config difftool.prompt false && + GIT_DIFFTOOL_PROMPT=true && + export GIT_DIFFTOOL_PROMPT && + + prompt=$(echo | git difftool --prompt branch | tail -1) && + prompt_given "$prompt" && + + restore_test_defaults +' + +# Test that we don't have to pass --no-prompt when difftool.prompt is false +test_expect_success 'difftool.prompt config variable is false' ' + git config difftool.prompt false && + + diff=$(git difftool branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + +# Test that the -y flag can override difftool.prompt = true +test_expect_success 'difftool.prompt can overridden with -y' ' + git config difftool.prompt true && + + diff=$(git difftool -y branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + +# Test that the --prompt flag can override difftool.prompt = false +test_expect_success 'difftool.prompt can overridden with --prompt' ' + git config difftool.prompt false && + + prompt=$(echo | git difftool --prompt branch | tail -1) && + prompt_given "$prompt" && + + restore_test_defaults +' + +# Test that the last flag passed on the command-line wins +test_expect_success 'difftool last flag wins' ' + diff=$(git difftool --prompt --no-prompt branch) && + test "$diff" = "branch" && + + restore_test_defaults && + + prompt=$(echo | git difftool --no-prompt --prompt branch | tail -1) && + prompt_given "$prompt" && + + restore_test_defaults +' + +# git-difftool falls back to git-mergetool config variables +# so test that behavior here +test_expect_success 'difftool + mergetool config variables' ' + remove_config_vars + git config merge.tool test-tool && + git config mergetool.test-tool.cmd "cat \$LOCAL" && + + diff=$(git difftool --no-prompt branch) && + test "$diff" = "branch" && + + # set merge.tool to something bogus, diff.tool to test-tool + git config merge.tool bogus-tool && + git config diff.tool test-tool && + + diff=$(git difftool --no-prompt branch) && + test "$diff" = "branch" && + + restore_test_defaults +' + +test_expect_success 'difftool.<tool>.path' ' + git config difftool.tkdiff.path echo && + diff=$(git difftool --tool=tkdiff --no-prompt branch) && + git config --unset difftool.tkdiff.path && + lines=$(echo "$diff" | grep file | wc -l) && + test "$lines" -eq 1 +' + +test_done diff --git a/t/t8003-blame.sh b/t/t8003-blame.sh index 966bb0a61a..13c25f1d52 100755 --- a/t/t8003-blame.sh +++ b/t/t8003-blame.sh @@ -129,4 +129,19 @@ test_expect_success 'blame wholesale copy and more' ' ' +test_expect_success 'blame path that used to be a directory' ' + mkdir path && + echo A A A A A >path/file && + echo B B B B B >path/elif && + git add path && + test_tick && + git commit -m "path was a directory" && + rm -fr path && + echo A A A A A >path && + git add path && + test_tick && + git commit -m "path is a regular file" && + git blame HEAD^.. -- path +' + test_done diff --git a/t/t8005-blame-i18n.sh b/t/t8005-blame-i18n.sh index 4470a92bb2..9cca14d080 100755 --- a/t/t8005-blame-i18n.sh +++ b/t/t8005-blame-i18n.sh @@ -4,7 +4,7 @@ test_description='git blame encoding conversion' . ./test-lib.sh . "$TEST_DIRECTORY"/t8005/utf8.txt -. "$TEST_DIRECTORY"/t8005/cp1251.txt +. "$TEST_DIRECTORY"/t8005/iso8859-5.txt . "$TEST_DIRECTORY"/t8005/sjis.txt test_expect_success 'setup the repository' ' @@ -13,14 +13,14 @@ test_expect_success 'setup the repository' ' git add file && git commit --author "$UTF8_NAME <utf8@localhost>" -m "$UTF8_MSG" && - echo "CP1251 LINE" >> file && + echo "ISO-8859-5 LINE" >> file && git add file && - git config i18n.commitencoding cp1251 && - git commit --author "$CP1251_NAME <cp1251@localhost>" -m "$CP1251_MSG" && + git config i18n.commitencoding ISO8859-5 && + git commit --author "$ISO8859_5_NAME <iso8859-5@localhost>" -m "$ISO8859_5_MSG" && echo "SJIS LINE" >> file && git add file && - git config i18n.commitencoding shift-jis && + git config i18n.commitencoding SJIS && git commit --author "$SJIS_NAME <sjis@localhost>" -m "$SJIS_MSG" ' @@ -36,24 +36,24 @@ EOF test_expect_success \ 'blame respects i18n.commitencoding' ' git blame --incremental file | \ - grep "^\(author\|summary\) " > actual && + egrep "^(author|summary) " > actual && test_cmp actual expected ' cat >expected <<EOF -author $CP1251_NAME -summary $CP1251_MSG -author $CP1251_NAME -summary $CP1251_MSG -author $CP1251_NAME -summary $CP1251_MSG +author $ISO8859_5_NAME +summary $ISO8859_5_MSG +author $ISO8859_5_NAME +summary $ISO8859_5_MSG +author $ISO8859_5_NAME +summary $ISO8859_5_MSG EOF test_expect_success \ 'blame respects i18n.logoutputencoding' ' - git config i18n.logoutputencoding cp1251 && + git config i18n.logoutputencoding ISO8859-5 && git blame --incremental file | \ - grep "^\(author\|summary\) " > actual && + egrep "^(author|summary) " > actual && test_cmp actual expected ' @@ -67,17 +67,17 @@ summary $UTF8_MSG EOF test_expect_success \ - 'blame respects --encoding=utf-8' ' - git blame --incremental --encoding=utf-8 file | \ - grep "^\(author\|summary\) " > actual && + 'blame respects --encoding=UTF-8' ' + git blame --incremental --encoding=UTF-8 file | \ + egrep "^(author|summary) " > actual && test_cmp actual expected ' cat >expected <<EOF author $SJIS_NAME summary $SJIS_MSG -author $CP1251_NAME -summary $CP1251_MSG +author $ISO8859_5_NAME +summary $ISO8859_5_MSG author $UTF8_NAME summary $UTF8_MSG EOF @@ -85,7 +85,7 @@ EOF test_expect_success \ 'blame respects --encoding=none' ' git blame --incremental --encoding=none file | \ - grep "^\(author\|summary\) " > actual && + egrep "^(author|summary) " > actual && test_cmp actual expected ' diff --git a/t/t8005/cp1251.txt b/t/t8005/cp1251.txt deleted file mode 100644 index ce41e98b81..0000000000 --- a/t/t8005/cp1251.txt +++ /dev/null @@ -1,2 +0,0 @@ -CP1251_NAME="Èâàí Ïåòðîâè÷ Ñèäîðîâ" -CP1251_MSG="Òåñòîâîå ñîîáùåíèå" diff --git a/t/t8005/iso8859-5.txt b/t/t8005/iso8859-5.txt new file mode 100644 index 0000000000..2e4b80c8df --- /dev/null +++ b/t/t8005/iso8859-5.txt @@ -0,0 +1,2 @@ +ISO8859_5_NAME="¸ÒÐÝ ¿ÕâàÞÒØç ÁØÔÞàÞÒ" +ISO8859_5_MSG="ÂÕáâÞÒÞÕ áÞÞÑéÕÝØÕ" diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index d7634187aa..9ce04fd472 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -3,6 +3,11 @@ test_description='git send-email' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git send-email tests, perl not available' + test_done +fi + PROG='git send-email' test_expect_success \ 'prepare reference tree' \ @@ -35,6 +40,47 @@ test_expect_success 'Extract patches' ' patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1` ' +# Test no confirm early to ensure remaining tests will not hang +test_no_confirm () { + rm -f no_confirm_okay + echo n | \ + GIT_SEND_EMAIL_NOTTY=1 \ + git send-email \ + --from="Example <from@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $@ \ + $patches > stdout && + test_must_fail grep "Send this email" stdout && + > no_confirm_okay +} + +# Exit immediately to prevent hang if a no-confirm test fails +check_no_confirm () { + test -f no_confirm_okay || { + say 'No confirm test failed; skipping remaining tests to prevent hanging' + test_done + } +} + +test_expect_success 'No confirm with --suppress-cc' ' + test_no_confirm --suppress-cc=sob +' +check_no_confirm + +test_expect_success 'No confirm with --confirm=never' ' + test_no_confirm --confirm=never +' +check_no_confirm + +# leave sendemail.confirm set to never after this so that none of the +# remaining tests prompt unintentionally. +test_expect_success 'No confirm with sendemail.confirm=never' ' + git config sendemail.confirm never && + test_no_confirm --compose --subject=foo +' +check_no_confirm + test_expect_success 'Send patches' ' git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors ' @@ -47,7 +93,7 @@ cat >expected <<\EOF EOF test_expect_success \ 'Verify commandline' \ - 'diff commandline1 expected' + 'test_cmp expected commandline1' cat >expected-show-all-headers <<\EOF 0001-Second.patch @@ -89,6 +135,19 @@ test_expect_success 'Show all headers' ' test_cmp expected-show-all-headers actual-show-all-headers ' +test_expect_success 'Prompting works' ' + clean_fake_sendmail && + (echo "Example <from@example.com>" + echo "to@example.com" + echo "" + ) | GIT_SEND_EMAIL_NOTTY=1 git send-email \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches \ + 2>errors && + grep "^From: Example <from@example.com>$" msgtxt1 && + grep "^To: to@example.com$" msgtxt1 +' + z8=zzzzzzzz z64=$z8$z8$z8$z8$z8$z8$z8$z8 z512=$z64$z64$z64$z64$z64$z64$z64$z64 @@ -175,15 +234,13 @@ test_set_editor "$(pwd)/fake-editor" test_expect_success '--compose works' ' clean_fake_sendmail && - echo y | \ - GIT_SEND_EMAIL_NOTTY=1 \ - git send-email \ - --compose --subject foo \ - --from="Example <nobody@example.com>" \ - --to=nobody@example.com \ - --smtp-server="$(pwd)/fake.sendmail" \ - $patches \ - 2>errors + git send-email \ + --compose --subject foo \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches \ + 2>errors ' test_expect_success 'first message is compose text' ' @@ -375,15 +432,117 @@ test_expect_success '--suppress-cc=cc' ' test_suppression cc ' +test_confirm () { + echo y | \ + GIT_SEND_EMAIL_NOTTY=1 \ + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $@ $patches > stdout && + grep "Send this email" stdout +} + +test_expect_success '--confirm=always' ' + test_confirm --confirm=always --suppress-cc=all +' + +test_expect_success '--confirm=auto' ' + test_confirm --confirm=auto +' + +test_expect_success '--confirm=cc' ' + test_confirm --confirm=cc +' + +test_expect_success '--confirm=compose' ' + test_confirm --confirm=compose --compose +' + +test_expect_success 'confirm by default (due to cc)' ' + CONFIRM=$(git config --get sendemail.confirm) && + git config --unset sendemail.confirm && + test_confirm + ret="$?" + git config sendemail.confirm ${CONFIRM:-never} + test $ret = "0" +' + +test_expect_success 'confirm by default (due to --compose)' ' + CONFIRM=$(git config --get sendemail.confirm) && + git config --unset sendemail.confirm && + test_confirm --suppress-cc=all --compose + ret="$?" + git config sendemail.confirm ${CONFIRM:-never} + test $ret = "0" +' + +test_expect_success 'confirm detects EOF (inform assumes y)' ' + CONFIRM=$(git config --get sendemail.confirm) && + git config --unset sendemail.confirm && + rm -fr outdir && + git format-patch -2 -o outdir && + GIT_SEND_EMAIL_NOTTY=1 \ + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/*.patch < /dev/null + ret="$?" + git config sendemail.confirm ${CONFIRM:-never} + test $ret = "0" +' + +test_expect_success 'confirm detects EOF (auto causes failure)' ' + CONFIRM=$(git config --get sendemail.confirm) && + git config sendemail.confirm auto && + GIT_SEND_EMAIL_NOTTY=1 && + export GIT_SEND_EMAIL_NOTTY && + test_must_fail git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches < /dev/null + ret="$?" + git config sendemail.confirm ${CONFIRM:-never} + test $ret = "0" +' + +test_expect_success 'confirm doesnt loop forever' ' + CONFIRM=$(git config --get sendemail.confirm) && + git config sendemail.confirm auto && + GIT_SEND_EMAIL_NOTTY=1 && + export GIT_SEND_EMAIL_NOTTY && + yes "bogus" | test_must_fail git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + $patches + ret="$?" + git config sendemail.confirm ${CONFIRM:-never} + test $ret = "0" +' + +test_expect_success 'utf8 Cc is rfc2047 encoded' ' + clean_fake_sendmail && + rm -fr outdir && + git format-patch -1 -o outdir --cc="à éìöú <utf8@example.com>" && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + outdir/*.patch && + grep "^Cc:" msgtxt1 | + grep "=?UTF-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>" +' + test_expect_success '--compose adds MIME for utf8 body' ' clean_fake_sendmail && (echo "#!$SHELL_PATH" && echo "echo utf8 body: à éìöú >>\"\$1\"" ) >fake-editor-utf8 && chmod +x fake-editor-utf8 && - echo y | \ GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \ - GIT_SEND_EMAIL_NOTTY=1 \ git send-email \ --compose --subject foo \ --from="Example <nobody@example.com>" \ @@ -391,7 +550,7 @@ test_expect_success '--compose adds MIME for utf8 body' ' --smtp-server="$(pwd)/fake.sendmail" \ $patches && grep "^utf8 body" msgtxt1 && - grep "^Content-Type: text/plain; charset=utf-8" msgtxt1 + grep "^Content-Type: text/plain; charset=UTF-8" msgtxt1 ' test_expect_success '--compose respects user mime type' ' @@ -405,9 +564,7 @@ test_expect_success '--compose respects user mime type' ' echo " echo utf8 body: à éìöú) >\"\$1\"" ) >fake-editor-utf8-mime && chmod +x fake-editor-utf8-mime && - echo y | \ GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \ - GIT_SEND_EMAIL_NOTTY=1 \ git send-email \ --compose --subject foo \ --from="Example <nobody@example.com>" \ @@ -416,14 +573,12 @@ test_expect_success '--compose respects user mime type' ' $patches && grep "^utf8 body" msgtxt1 && grep "^Content-Type: text/plain; charset=iso-8859-1" msgtxt1 && - ! grep "^Content-Type: text/plain; charset=utf-8" msgtxt1 + ! grep "^Content-Type: text/plain; charset=UTF-8" msgtxt1 ' test_expect_success '--compose adds MIME for utf8 subject' ' clean_fake_sendmail && - echo y | \ GIT_EDITOR="\"$(pwd)/fake-editor\"" \ - GIT_SEND_EMAIL_NOTTY=1 \ git send-email \ --compose --subject utf8-sübjëct \ --from="Example <nobody@example.com>" \ @@ -431,7 +586,7 @@ test_expect_success '--compose adds MIME for utf8 subject' ' --smtp-server="$(pwd)/fake.sendmail" \ $patches && grep "^fake edit" msgtxt1 && - grep "^Subject: =?utf-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1 + grep "^Subject: =?UTF-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1 ' test_expect_success 'detects ambiguous reference/file conflict' ' @@ -445,7 +600,7 @@ test_expect_success 'detects ambiguous reference/file conflict' ' test_expect_success 'feed two files' ' rm -fr outdir && git format-patch -2 -o outdir && - GIT_SEND_EMAIL_NOTTY=1 git send-email \ + git send-email \ --dry-run \ --from="Example <nobody@example.com>" \ --to=nobody@example.com \ @@ -461,9 +616,30 @@ test_expect_success 'in-reply-to but no threading' ' --from="Example <nobody@example.com>" \ --to=nobody@example.com \ --in-reply-to="<in-reply-id@example.com>" \ - --no-thread \ + --nothread \ $patches | grep "In-Reply-To: <in-reply-id@example.com>" ' +test_expect_success 'no in-reply-to and no threading' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --nothread \ + $patches $patches >stdout && + ! grep "In-Reply-To: " stdout +' + +test_expect_success 'threading but no chain-reply-to' ' + git send-email \ + --dry-run \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --thread \ + --nochain-reply-to \ + $patches $patches >stdout && + grep "In-Reply-To: " stdout +' + test_done diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index bb921af56a..64aa7e2f10 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -6,19 +6,19 @@ test_description='git svn basic tests' GIT_SVN_LC_ALL=${LC_ALL:-$LANG} +. ./lib-git-svn.sh + +say 'define NO_SVN_TESTS to skip git svn tests' + case "$GIT_SVN_LC_ALL" in *.UTF-8) - have_utf8=t + test_set_prereq UTF8 ;; *) - have_utf8= + say "UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)" ;; esac -. ./lib-git-svn.sh - -say 'define NO_SVN_TESTS to skip git svn tests' - test_expect_success \ 'initialize git svn' ' mkdir import && @@ -31,7 +31,7 @@ test_expect_success \ echo "zzz" > bar/zzz && echo "#!/bin/sh" > exec.sh && chmod +x exec.sh && - svn import -m "import for git svn" . "$svnrepo" >/dev/null && + svn_cmd import -m "import for git svn" . "$svnrepo" >/dev/null && cd .. && rm -rf import && git svn init "$svnrepo"' @@ -51,7 +51,7 @@ test_expect_success "$name" ' git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch && - svn up "$SVN_TREE" && + svn_cmd up "$SVN_TREE" && test -d "$SVN_TREE"/dir && test ! -d "$SVN_TREE"/dir/a' @@ -118,7 +118,7 @@ test_expect_success "$name" ' git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch5 && - svn up "$SVN_TREE" && + svn_cmd up "$SVN_TREE" && test ! -x "$SVN_TREE"/exec.sh' @@ -129,7 +129,7 @@ test_expect_success "$name" ' git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch5 && - svn up "$SVN_TREE" && + svn_cmd up "$SVN_TREE" && test -x "$SVN_TREE"/exec.sh' @@ -141,7 +141,7 @@ test_expect_success "$name" ' git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch5 && - svn up "$SVN_TREE" && + svn_cmd up "$SVN_TREE" && test -L "$SVN_TREE"/exec.sh' name='new symlink is added to a file that was also just made executable' @@ -153,7 +153,7 @@ test_expect_success "$name" ' git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch5 && - svn up "$SVN_TREE" && + svn_cmd up "$SVN_TREE" && test -x "$SVN_TREE"/bar/zzz && test -L "$SVN_TREE"/exec-2.sh' @@ -166,25 +166,20 @@ test_expect_success "$name" ' git commit -m "$name" && git svn set-tree --find-copies-harder --rmdir \ ${remotes_git_svn}..mybranch5 && - svn up "$SVN_TREE" && + svn_cmd up "$SVN_TREE" && test -f "$SVN_TREE"/exec-2.sh && test ! -L "$SVN_TREE"/exec-2.sh && test_cmp help "$SVN_TREE"/exec-2.sh' -if test "$have_utf8" = t -then - name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL" - LC_ALL="$GIT_SVN_LC_ALL" - export LC_ALL - test_expect_success "$name" " - echo '# hello' >> exec-2.sh && - git update-index exec-2.sh && - git commit -m 'éïâˆ' && - git svn set-tree HEAD" - unset LC_ALL -else - say "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)" -fi +name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL" +LC_ALL="$GIT_SVN_LC_ALL" +export LC_ALL +test_expect_success UTF8 "$name" " + echo '# hello' >> exec-2.sh && + git update-index exec-2.sh && + git commit -m 'éïâˆ' && + git svn set-tree HEAD" +unset LC_ALL name='test fetch functionality (svn => git) with alternate GIT_SVN_ID' GIT_SVN_ID=alt @@ -197,7 +192,7 @@ test_expect_success "$name" \ name='check imported tree checksums expected tree checksums' rm -f expected -if test "$have_utf8" = t +if test_have_prereq UTF8 then echo tree bf522353586b1b883488f2bc73dab0d9f774b9a9 > expected fi diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 1e31d6ea72..9da4178c94 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -48,7 +48,7 @@ EOF printf "\r\n" > empty_crlf a_empty_crlf=`git hash-object -w empty_crlf` - svn import --no-auto-props -m 'import for git svn' . "$svnrepo" >/dev/null + svn_cmd import --no-auto-props -m 'import for git svn' . "$svnrepo" >/dev/null cd .. rm -rf import @@ -57,13 +57,13 @@ test_expect_success 'setup some commits to svn' \ 'cd test_wc && echo Greetings >> kw.c && poke kw.c && - svn commit -m "Not yet an Id" && + svn_cmd commit -m "Not yet an Id" && echo Hello world >> kw.c && poke kw.c && - svn commit -m "Modified file, but still not yet an Id" && - svn propset svn:keywords Id kw.c && + svn_cmd commit -m "Modified file, but still not yet an Id" && + svn_cmd propset svn:keywords Id kw.c && poke kw.c && - svn commit -m "Propset Id" && + svn_cmd commit -m "Propset Id" && cd ..' test_expect_success 'initialize git svn' 'git svn init "$svnrepo"' @@ -83,16 +83,16 @@ test_expect_success 'raw $Id$ found in kw.c' "test '$expect' = '$got'" test_expect_success "propset CR on crlf files" \ 'cd test_wc && - svn propset svn:eol-style CR empty && - svn propset svn:eol-style CR crlf && - svn propset svn:eol-style CR ne_crlf && - svn commit -m "propset CR on crlf files" && + svn_cmd propset svn:eol-style CR empty && + svn_cmd propset svn:eol-style CR crlf && + svn_cmd propset svn:eol-style CR ne_crlf && + svn_cmd commit -m "propset CR on crlf files" && cd ..' test_expect_success 'fetch and pull latest from svn and checkout a new wc' \ 'git svn fetch && git pull . ${remotes_git_svn} && - svn co "$svnrepo" new_wc' + svn_cmd co "$svnrepo" new_wc' for i in crlf ne_crlf lf ne_lf cr ne_cr empty_cr empty_lf empty empty_crlf do @@ -106,11 +106,11 @@ cd test_wc a_cr=`printf '$Id$\r\nHello\r\nWorld\r\n' | git hash-object --stdin` a_ne_cr=`printf '$Id$\r\nHello\r\nWorld' | git hash-object --stdin` test_expect_success 'Set CRLF on cr files' \ - 'svn propset svn:eol-style CRLF cr && - svn propset svn:eol-style CRLF ne_cr && - svn propset svn:keywords Id cr && - svn propset svn:keywords Id ne_cr && - svn commit -m "propset CRLF on cr files"' + 'svn_cmd propset svn:eol-style CRLF cr && + svn_cmd propset svn:eol-style CRLF ne_cr && + svn_cmd propset svn:keywords Id cr && + svn_cmd propset svn:keywords Id ne_cr && + svn_cmd commit -m "propset CRLF on cr files"' cd .. test_expect_success 'fetch and pull latest from svn' \ 'git svn fetch && git pull . ${remotes_git_svn}' @@ -140,10 +140,10 @@ test_expect_success 'test show-ignore' " cd test_wc && mkdir -p deeply/nested/directory && touch deeply/nested/directory/.keep && - svn add deeply && - svn up && - svn propset -R svn:ignore 'no-such-file*' . - svn commit -m 'propset svn:ignore' + svn_cmd add deeply && + svn_cmd up && + svn_cmd propset -R svn:ignore 'no-such-file*' . + svn_cmd commit -m 'propset svn:ignore' cd .. && git svn show-ignore > show-ignore.got && cmp show-ignore.expect show-ignore.got diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh index e223218015..028fb19e09 100755 --- a/t/t9102-git-svn-deep-rmdir.sh +++ b/t/t9102-git-svn-deep-rmdir.sh @@ -9,7 +9,7 @@ test_expect_success 'initialize repo' ' mkdir -p deeply/nested/directory/number/2 && echo foo > deeply/nested/directory/number/1/file && echo foo > deeply/nested/directory/number/2/another && - svn import -m "import for git svn" . "$svnrepo" && + svn_cmd import -m "import for git svn" . "$svnrepo" && cd .. ' @@ -23,7 +23,7 @@ test_expect_success 'Try a commit on rmdir' ' git rm -f deeply/nested/directory/number/2/another && git commit -a -m "remove another" && git svn set-tree --rmdir HEAD && - svn ls -R "$svnrepo" | grep ^deeply/nested/directory/number/1 + svn_cmd ls -R "$svnrepo" | grep ^deeply/nested/directory/number/1 ' diff --git a/t/t9103-git-svn-tracked-directory-removed.sh b/t/t9103-git-svn-tracked-directory-removed.sh index 963dd95e4a..3413164cb1 100755 --- a/t/t9103-git-svn-tracked-directory-removed.sh +++ b/t/t9103-git-svn-tracked-directory-removed.sh @@ -10,15 +10,15 @@ test_expect_success 'make history for tracking' ' mkdir import && mkdir import/trunk && echo hello >> import/trunk/README && - svn import -m initial import "$svnrepo" && + svn_cmd import -m initial import "$svnrepo" && rm -rf import && - svn co "$svnrepo"/trunk trunk && + svn_cmd co "$svnrepo"/trunk trunk && echo bye bye >> trunk/README && - svn rm -m "gone" "$svnrepo"/trunk && + svn_cmd rm -m "gone" "$svnrepo"/trunk && rm -rf trunk && mkdir trunk && echo "new" > trunk/FOLLOWME && - svn import -m "new trunk" trunk "$svnrepo"/trunk + svn_cmd import -m "new trunk" trunk "$svnrepo"/trunk ' test_expect_success 'clone repo with git' ' diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index ab9fa32220..78610b61e6 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -11,18 +11,18 @@ test_expect_success 'initialize repo' ' cd import && mkdir -p trunk && echo hello > trunk/readme && - svn import -m "initial" . "$svnrepo" && + svn_cmd import -m "initial" . "$svnrepo" && cd .. && - svn co "$svnrepo" wc && + svn_cmd co "$svnrepo" wc && cd wc && echo world >> trunk/readme && poke trunk/readme && - svn commit -m "another commit" && - svn up && - svn mv trunk thunk && + svn_cmd commit -m "another commit" && + svn_cmd up && + svn_cmd mv trunk thunk && echo goodbye >> thunk/readme && poke thunk/readme && - svn commit -m "bye now" && + svn_cmd commit -m "bye now" && cd .. ' @@ -51,7 +51,7 @@ test_expect_success 'init and fetch from one svn-remote' ' ' test_expect_success 'follow deleted parent' ' - (svn cp -m "resurrecting trunk as junk" \ + (svn_cmd cp -m "resurrecting trunk as junk" \ "$svnrepo"/trunk@2 "$svnrepo"/junk || svn cp -m "resurrecting trunk as junk" \ -r2 "$svnrepo"/trunk "$svnrepo"/junk) && @@ -97,8 +97,8 @@ test_expect_success 'follow higher-level parent' ' ' test_expect_success 'follow deleted directory' ' - svn mv -m "bye!" "$svnrepo"/glob/blob/hi "$svnrepo"/glob/blob/bye && - svn rm -m "remove glob" "$svnrepo"/glob && + svn_cmd mv -m "bye!" "$svnrepo"/glob/blob/hi "$svnrepo"/glob/blob/bye && + svn_cmd rm -m "remove glob" "$svnrepo"/glob && git svn init --minimize-url -i glob "$svnrepo"/glob && git svn fetch -i glob && test "`git cat-file blob refs/remotes/glob:blob/bye`" = hi && @@ -120,7 +120,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' ' cd import && svn import -m "r9270 test" . "$svnrepo"/r9270 && cd .. && - svn co "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl r9270 && + svn_cmd co "$svnrepo"/r9270/trunk/subversion/bindings/swig/perl r9270 && cd r9270 && svn mkdir native && svn mv t native/t && @@ -138,7 +138,7 @@ test_expect_success 'follow-parent avoids deleting relevant info' ' ' test_expect_success "track initial change if it was only made to parent" ' - svn cp -m "wheee!" "$svnrepo"/r9270/trunk "$svnrepo"/r9270/drunk && + svn_cmd cp -m "wheee!" "$svnrepo"/r9270/trunk "$svnrepo"/r9270/drunk && git svn init --minimize-url -i r9270-d \ "$svnrepo"/r9270/drunk/subversion/bindings/swig/perl/native/t && git svn fetch -i r9270-d && @@ -152,20 +152,20 @@ test_expect_success "track initial change if it was only made to parent" ' test_expect_success "follow-parent is atomic" ' ( cd wc && - svn up && - svn mkdir stunk && + svn_cmd up && + svn_cmd mkdir stunk && echo "trunk stunk" > stunk/readme && - svn add stunk/readme && - svn ci -m "trunk stunk" && + svn_cmd add stunk/readme && + svn_cmd ci -m "trunk stunk" && echo "stunk like junk" >> stunk/readme && - svn ci -m "really stunk" && + svn_cmd ci -m "really stunk" && echo "stink stank stunk" >> stunk/readme && - svn ci -m "even the grinch agrees" + svn_cmd ci -m "even the grinch agrees" ) && - svn copy -m "stunk flunked" "$svnrepo"/stunk "$svnrepo"/flunk && + svn_cmd copy -m "stunk flunked" "$svnrepo"/stunk "$svnrepo"/flunk && { svn cp -m "early stunk flunked too" \ "$svnrepo"/stunk@17 "$svnrepo"/flunked || - svn cp -m "early stunk flunked too" \ + svn_cmd cp -m "early stunk flunked too" \ -r17 "$svnrepo"/stunk "$svnrepo"/flunked; } && git svn init --minimize-url -i stunk "$svnrepo"/stunk && git svn fetch -i stunk && @@ -192,7 +192,7 @@ test_expect_success "follow-parent is atomic" ' ' test_expect_success "track multi-parent paths" ' - svn cp -m "resurrect /glob" "$svnrepo"/r9270 "$svnrepo"/glob && + svn_cmd cp -m "resurrect /glob" "$svnrepo"/r9270 "$svnrepo"/glob && git svn multi-fetch && test `git cat-file commit refs/remotes/glob | \ grep "^parent " | wc -l` -eq 2 diff --git a/t/t9105-git-svn-commit-diff.sh b/t/t9105-git-svn-commit-diff.sh index ba99abb6d9..dd48e9cba8 100755 --- a/t/t9105-git-svn-commit-diff.sh +++ b/t/t9105-git-svn-commit-diff.sh @@ -8,7 +8,7 @@ test_expect_success 'initialize repo' ' mkdir import && cd import && echo hello > readme && - svn import -m "initial" . "$svnrepo" && + svn_cmd import -m "initial" . "$svnrepo" && cd .. && echo hello > readme && git update-index --add readme && @@ -27,16 +27,16 @@ prev=`git rev-parse --verify HEAD^1` test_expect_success 'test the commit-diff command' ' test -n "$prev" && test -n "$head" && git svn commit-diff -r1 "$prev" "$head" "$svnrepo" && - svn co "$svnrepo" wc && + svn_cmd co "$svnrepo" wc && cmp readme wc/readme ' test_expect_success 'commit-diff to a sub-directory (with git svn config)' ' - svn import -m "sub-directory" import "$svnrepo"/subdir && + svn_cmd import -m "sub-directory" import "$svnrepo"/subdir && git svn init --minimize-url "$svnrepo"/subdir && git svn fetch && git svn commit-diff -r3 "$prev" "$head" && - svn cat "$svnrepo"/subdir/readme > readme.2 && + svn_cmd cat "$svnrepo"/subdir/readme > readme.2 && cmp readme readme.2 ' diff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh index 6eb0fd85c8..12f21b700e 100755 --- a/t/t9106-git-svn-commit-diff-clobber.sh +++ b/t/t9106-git-svn-commit-diff-clobber.sh @@ -8,18 +8,18 @@ test_expect_success 'initialize repo' ' mkdir import && cd import && echo initial > file && - svn import -m "initial" . "$svnrepo" && + svn_cmd import -m "initial" . "$svnrepo" && cd .. && echo initial > file && git update-index --add file && git commit -a -m "initial" ' test_expect_success 'commit change from svn side' ' - svn co "$svnrepo" t.svn && + svn_cmd co "$svnrepo" t.svn && cd t.svn && echo second line from svn >> file && poke file && - svn commit -m "second line from svn" && + svn_cmd commit -m "second line from svn" && cd .. && rm -rf t.svn ' @@ -43,11 +43,11 @@ test_expect_success 'dcommit fails to commit because of conflict' ' git svn init "$svnrepo" && git svn fetch && git reset --hard refs/${remotes_git_svn} && - svn co "$svnrepo" t.svn && + svn_cmd co "$svnrepo" t.svn && cd t.svn && echo fourth line from svn >> file && poke file && - svn commit -m "fourth line from svn" && + svn_cmd commit -m "fourth line from svn" && cd .. && rm -rf t.svn && echo "fourth line from git" >> file && @@ -67,11 +67,11 @@ test_expect_success 'dcommit does the svn equivalent of an index merge' " " test_expect_success 'commit another change from svn side' ' - svn co "$svnrepo" t.svn && + svn_cmd co "$svnrepo" t.svn && cd t.svn && echo third line from svn >> file && poke file && - svn commit -m "third line from svn" && + svn_cmd commit -m "third line from svn" && cd .. && rm -rf t.svn ' diff --git a/t/t9107-git-svn-migrate.sh b/t/t9107-git-svn-migrate.sh index acad16a6f0..3a9e07768d 100755 --- a/t/t9107-git-svn-migrate.sh +++ b/t/t9107-git-svn-migrate.sh @@ -12,7 +12,7 @@ test_expect_success 'setup old-looking metadata' ' mkdir -p $i && \ echo hello >> $i/README || exit 1 done && \ - svn import -m test . "$svnrepo" + svn_cmd import -m test . "$svnrepo" cd .. && git svn init "$svnrepo" && git svn fetch && diff --git a/t/t9108-git-svn-glob.sh b/t/t9108-git-svn-glob.sh index d8582b1aa5..d732d31302 100755 --- a/t/t9108-git-svn-glob.sh +++ b/t/t9108-git-svn-glob.sh @@ -14,30 +14,30 @@ test_expect_success 'test refspec globbing' ' mkdir -p trunk/src/a trunk/src/b trunk/doc && echo "hello world" > trunk/src/a/readme && echo "goodbye world" > trunk/src/b/readme && - svn import -m "initial" trunk "$svnrepo"/trunk && - svn co "$svnrepo" tmp && + svn_cmd import -m "initial" trunk "$svnrepo"/trunk && + svn_cmd co "$svnrepo" tmp && ( cd tmp && mkdir branches tags && - svn add branches tags && - svn cp trunk branches/start && - svn commit -m "start a new branch" && - svn up && + svn_cmd add branches tags && + svn_cmd cp trunk branches/start && + svn_cmd commit -m "start a new branch" && + svn_cmd up && echo "hi" >> branches/start/src/b/readme && poke branches/start/src/b/readme && echo "hey" >> branches/start/src/a/readme && poke branches/start/src/a/readme && - svn commit -m "hi" && - svn up && - svn cp branches/start tags/end && + svn_cmd commit -m "hi" && + svn_cmd up && + svn_cmd cp branches/start tags/end && echo "bye" >> tags/end/src/b/readme && poke tags/end/src/b/readme && echo "aye" >> tags/end/src/a/readme && poke tags/end/src/a/readme && - svn commit -m "the end" && + svn_cmd commit -m "the end" && echo "byebye" >> tags/end/src/b/readme && poke tags/end/src/b/readme && - svn commit -m "nothing to see here" + svn_cmd commit -m "nothing to see here" ) && git config --add svn-remote.svn.url "$svnrepo" && git config --add svn-remote.svn.fetch \ @@ -72,7 +72,7 @@ test_expect_success 'test left-hand-side only globbing' ' cd tmp && echo "try try" >> tags/end/src/b/readme && poke tags/end/src/b/readme && - svn commit -m "try to try" + svn_cmd commit -m "try to try" ) && git svn fetch two && test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 && @@ -102,7 +102,7 @@ test_expect_success 'test disallow multi-globs' ' cd tmp && echo "try try" >> tags/end/src/b/readme && poke tags/end/src/b/readme && - svn commit -m "try to try" + svn_cmd commit -m "try to try" ) && test_must_fail git svn fetch three 2> stderr.three && test_cmp expect.three stderr.three diff --git a/t/t9108-git-svn-multi-glob.sh b/t/t9109-git-svn-multi-glob.sh index 8f79c3f251..c318f9f946 100755 --- a/t/t9108-git-svn-multi-glob.sh +++ b/t/t9109-git-svn-multi-glob.sh @@ -14,30 +14,30 @@ test_expect_success 'test refspec globbing' ' mkdir -p trunk/src/a trunk/src/b trunk/doc && echo "hello world" > trunk/src/a/readme && echo "goodbye world" > trunk/src/b/readme && - svn import -m "initial" trunk "$svnrepo"/trunk && - svn co "$svnrepo" tmp && + svn_cmd import -m "initial" trunk "$svnrepo"/trunk && + svn_cmd co "$svnrepo" tmp && ( cd tmp && mkdir branches branches/v1 tags && - svn add branches tags && - svn cp trunk branches/v1/start && - svn commit -m "start a new branch" && - svn up && + svn_cmd add branches tags && + svn_cmd cp trunk branches/v1/start && + svn_cmd commit -m "start a new branch" && + svn_cmd up && echo "hi" >> branches/v1/start/src/b/readme && poke branches/v1/start/src/b/readme && echo "hey" >> branches/v1/start/src/a/readme && poke branches/v1/start/src/a/readme && - svn commit -m "hi" && - svn up && - svn cp branches/v1/start tags/end && + svn_cmd commit -m "hi" && + svn_cmd up && + svn_cmd cp branches/v1/start tags/end && echo "bye" >> tags/end/src/b/readme && poke tags/end/src/b/readme && echo "aye" >> tags/end/src/a/readme && poke tags/end/src/a/readme && - svn commit -m "the end" && + svn_cmd commit -m "the end" && echo "byebye" >> tags/end/src/b/readme && poke tags/end/src/b/readme && - svn commit -m "nothing to see here" + svn_cmd commit -m "nothing to see here" ) && git config --add svn-remote.svn.url "$svnrepo" && git config --add svn-remote.svn.fetch \ @@ -72,7 +72,7 @@ test_expect_success 'test left-hand-side only globbing' ' cd tmp && echo "try try" >> tags/end/src/b/readme && poke tags/end/src/b/readme && - svn commit -m "try to try" + svn_cmd commit -m "try to try" ) && git svn fetch two && test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 && @@ -97,25 +97,25 @@ test_expect_success 'test another branch' ' ( cd tmp && mkdir branches/v2 && - svn add branches/v2 && - svn cp trunk branches/v2/start && - svn commit -m "Another versioned branch" && - svn up && + svn_cmd add branches/v2 && + svn_cmd cp trunk branches/v2/start && + svn_cmd commit -m "Another versioned branch" && + svn_cmd up && echo "hello" >> branches/v2/start/src/b/readme && poke branches/v2/start/src/b/readme && echo "howdy" >> branches/v2/start/src/a/readme && poke branches/v2/start/src/a/readme && - svn commit -m "Changed 2 in v2/start" && - svn up && - svn cp branches/v2/start tags/next && + svn_cmd commit -m "Changed 2 in v2/start" && + svn_cmd up && + svn_cmd cp branches/v2/start tags/next && echo "bye" >> tags/next/src/b/readme && poke tags/next/src/b/readme && echo "aye" >> tags/next/src/a/readme && poke tags/next/src/a/readme && - svn commit -m "adding more" && + svn_cmd commit -m "adding more" && echo "byebye" >> tags/next/src/b/readme && poke tags/next/src/b/readme && - svn commit -m "adios" + svn_cmd commit -m "adios" ) && git config --add svn-remote.four.url "$svnrepo" && git config --add svn-remote.four.fetch trunk:refs/remotes/four/trunk && @@ -151,7 +151,7 @@ test_expect_success 'test disallow multiple globs' ' cd tmp && echo "try try" >> tags/end/src/b/readme && poke tags/end/src/b/readme && - svn commit -m "try to try" + svn_cmd commit -m "try to try" ) && test_must_fail git svn fetch three 2> stderr.three && test_cmp expect.three stderr.three diff --git a/t/t9113-git-svn-dcommit-new-file.sh b/t/t9113-git-svn-dcommit-new-file.sh index e9b6128b3f..e8479cec7a 100755 --- a/t/t9113-git-svn-dcommit-new-file.sh +++ b/t/t9113-git-svn-dcommit-new-file.sh @@ -15,7 +15,7 @@ test_description='git svn dcommit new files over svn:// test' require_svnserve test_expect_success 'start tracking an empty repo' ' - svn mkdir -m "empty dir" "$svnrepo"/empty-dir && + svn_cmd mkdir -m "empty dir" "$svnrepo"/empty-dir && echo "[general]" > "$rawsvnrepo"/conf/svnserve.conf && echo anon-access = write >> "$rawsvnrepo"/conf/svnserve.conf && start_svnserve && diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh index 17b2855c4f..84f7c9b4bb 100755 --- a/t/t9114-git-svn-dcommit-merge.sh +++ b/t/t9114-git-svn-dcommit-merge.sh @@ -35,12 +35,12 @@ EOF } test_expect_success 'setup svn repository' ' - svn co "$svnrepo" mysvnwork && + svn_cmd co "$svnrepo" mysvnwork && mkdir -p mysvnwork/trunk && cd mysvnwork && big_text_block >> trunk/README && - svn add trunk && - svn ci -m "first commit" trunk && + svn_cmd add trunk && + svn_cmd ci -m "first commit" trunk && cd .. ' diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh index fd6d1d2046..0374a7476b 100755 --- a/t/t9116-git-svn-log.sh +++ b/t/t9116-git-svn-log.sh @@ -14,7 +14,7 @@ test_expect_success 'setup repository and import' ' mkdir -p $i && \ echo hello >> $i/README || exit 1 done && \ - svn import -m test . "$svnrepo" + svn_cmd import -m test . "$svnrepo" cd .. && git svn init "$svnrepo" -T trunk -b branches -t tags && git svn fetch && diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh index dde46cd92f..b7ef9e2589 100755 --- a/t/t9117-git-svn-init-clone.sh +++ b/t/t9117-git-svn-init-clone.sh @@ -16,7 +16,7 @@ cd tmp test_expect_success 'setup svnrepo' ' mkdir project project/trunk project/branches project/tags && echo foo > project/trunk/foo && - svn import -m "$test_description" project "$svnrepo"/project && + svn_cmd import -m "$test_description" project "$svnrepo"/project && rm -rf project ' diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh index 7a7c128687..ac52bff0ef 100755 --- a/t/t9118-git-svn-funky-branch-names.sh +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -13,13 +13,13 @@ scary_ref='Abo-Uebernahme%20(Bug%20#994)' test_expect_success 'setup svnrepo' ' mkdir project project/trunk project/branches project/tags && echo foo > project/trunk/foo && - svn import -m "$test_description" project "$svnrepo/pr ject" && + svn_cmd import -m "$test_description" project "$svnrepo/pr ject" && rm -rf project && - svn cp -m "fun" "$svnrepo/pr ject/trunk" \ + svn_cmd cp -m "fun" "$svnrepo/pr ject/trunk" \ "$svnrepo/pr ject/branches/fun plugin" && - svn cp -m "more fun!" "$svnrepo/pr ject/branches/fun plugin" \ + svn_cmd cp -m "more fun!" "$svnrepo/pr ject/branches/fun plugin" \ "$svnrepo/pr ject/branches/more fun plugin!" && - svn cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \ + svn_cmd cp -m "scary" "$svnrepo/pr ject/branches/fun plugin" \ "$svnrepo/pr ject/branches/$scary_uri" && start_httpd ' diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index 27dd7c273a..95741cbbac 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -7,7 +7,7 @@ test_description='git svn info' . ./lib-git-svn.sh # Tested with: svn, version 1.4.4 (r25188) -v=`svn --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'` +v=`svn_cmd --version | sed -n -e 's/^svn, version \(1\.[0-9]*\.[0-9]*\).*$/\1/p'` case $v in 1.[45].*) ;; @@ -31,7 +31,7 @@ ptouch() { my $atime = $mtime; utime $atime, $mtime, $git_file; } - ' "`svn info $2 | grep '^Text Last Updated:'`" "$1" + ' "`svn_cmd info $2 | grep '^Text Last Updated:'`" "$1" } quoted_svnrepo="$(echo $svnrepo | sed 's/ /%20/')" @@ -45,14 +45,14 @@ test_expect_success 'setup repository and import' ' mkdir directory && touch directory/.placeholder && ln -s directory symlink-directory && - svn import -m "initial" . "$svnrepo" && + svn_cmd import -m "initial" . "$svnrepo" && cd .. && - svn co "$svnrepo" svnwc && + svn_cmd co "$svnrepo" svnwc && cd svnwc && echo foo > foo && - svn add foo && - svn commit -m "change outside directory" && - svn update && + svn_cmd add foo && + svn_cmd commit -m "change outside directory" && + svn_cmd update && cd .. && mkdir gitwc && cd gitwc && @@ -143,7 +143,7 @@ test_expect_success 'info added-file' " cp gitwc/added-file svnwc/added-file && ptouch gitwc/added-file svnwc/added-file && cd svnwc && - svn add added-file > /dev/null && + svn_cmd add added-file > /dev/null && cd .. && (cd svnwc; svn info added-file) > expected.info-added-file && (cd gitwc; git svn info added-file) > actual.info-added-file && @@ -160,7 +160,7 @@ test_expect_success 'info added-directory' " ptouch gitwc/added-directory svnwc/added-directory && touch gitwc/added-directory/.placeholder && cd svnwc && - svn add added-directory > /dev/null && + svn_cmd add added-directory > /dev/null && cd .. && cd gitwc && git add added-directory && @@ -184,7 +184,7 @@ test_expect_success 'info added-symlink-file' " cd .. && cd svnwc && ln -s added-file added-symlink-file && - svn add added-symlink-file > /dev/null && + svn_cmd add added-symlink-file > /dev/null && cd .. && ptouch gitwc/added-symlink-file svnwc/added-symlink-file && (cd svnwc; svn info added-symlink-file) \ @@ -207,7 +207,7 @@ test_expect_success 'info added-symlink-directory' " cd .. && cd svnwc && ln -s added-directory added-symlink-directory && - svn add added-symlink-directory > /dev/null && + svn_cmd add added-symlink-directory > /dev/null && cd .. && ptouch gitwc/added-symlink-directory svnwc/added-symlink-directory && (cd svnwc; svn info added-symlink-directory) \ @@ -233,7 +233,7 @@ test_expect_success 'info deleted-file' " git rm -f file > /dev/null && cd .. && cd svnwc && - svn rm --force file > /dev/null && + svn_cmd rm --force file > /dev/null && cd .. && (cd svnwc; svn info file) | sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ @@ -254,7 +254,7 @@ test_expect_success 'info deleted-directory' " git rm -r -f directory > /dev/null && cd .. && cd svnwc && - svn rm --force directory > /dev/null && + svn_cmd rm --force directory > /dev/null && cd .. && (cd svnwc; svn info directory) | sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ @@ -275,7 +275,7 @@ test_expect_success 'info deleted-symlink-file' " git rm -f symlink-file > /dev/null && cd .. && cd svnwc && - svn rm --force symlink-file > /dev/null && + svn_cmd rm --force symlink-file > /dev/null && cd .. && (cd svnwc; svn info symlink-file) | sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ @@ -297,7 +297,7 @@ test_expect_success 'info deleted-symlink-directory' " git rm -f symlink-directory > /dev/null && cd .. && cd svnwc && - svn rm --force symlink-directory > /dev/null && + svn_cmd rm --force symlink-directory > /dev/null && cd .. && (cd svnwc; svn info symlink-directory) | sed -e 's/^\(Text Last Updated:\).*/\1 TEXT-LAST-UPDATED-STRING/' \ diff --git a/t/t9120-git-svn-clone-with-percent-escapes.sh b/t/t9120-git-svn-clone-with-percent-escapes.sh index ef2c0523cd..f159ab689b 100755 --- a/t/t9120-git-svn-clone-with-percent-escapes.sh +++ b/t/t9120-git-svn-clone-with-percent-escapes.sh @@ -9,22 +9,17 @@ test_description='git svn clone with percent escapes' test_expect_success 'setup svnrepo' ' mkdir project project/trunk project/branches project/tags && echo foo > project/trunk/foo && - svn import -m "$test_description" project "$svnrepo/pr ject" && + svn_cmd import -m "$test_description" project "$svnrepo/pr ject" && rm -rf project && start_httpd ' -if test "$SVN_HTTPD_PORT" = "" -then - test_expect_failure 'test clone with percent escapes - needs SVN_HTTPD_PORT set' 'false' -else - test_expect_success 'test clone with percent escapes' ' - git svn clone "$svnrepo/pr%20ject" clone && - cd clone && - git rev-parse refs/${remotes_git_svn} && - cd .. - ' -fi +test_expect_success 'test clone with percent escapes' ' + git svn clone "$svnrepo/pr%20ject" clone && + cd clone && + git rev-parse refs/${remotes_git_svn} && + cd .. +' stop_httpd diff --git a/t/t9122-git-svn-author.sh b/t/t9122-git-svn-author.sh index 1b1cf47281..30013b7bb9 100755 --- a/t/t9122-git-svn-author.sh +++ b/t/t9122-git-svn-author.sh @@ -4,12 +4,12 @@ test_description='git svn authorship' . ./lib-git-svn.sh test_expect_success 'setup svn repository' ' - svn checkout "$svnrepo" work.svn && + svn_cmd checkout "$svnrepo" work.svn && ( cd work.svn && echo >file - svn add file - svn commit -m "first commit" file + svn_cmd add file + svn_cmd commit -m "first commit" file ) ' @@ -74,10 +74,10 @@ test_expect_success 'interact with it via git svn' ' # Make sure there are no svn commit messages with excess blank lines ( cd work.svn && - svn up && + svn_cmd up && - test $(svn log -r2:2 | wc -l) = 5 && - test $(svn log -r4:4 | wc -l) = 7 + test $(svn_cmd log -r2:2 | wc -l) = 5 && + test $(svn_cmd log -r4:4 | wc -l) = 7 ) ' diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh index cf0415274c..045521615c 100755 --- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh +++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh @@ -10,7 +10,7 @@ test_description='git svn respects rewriteRoot during rebuild' mkdir import cd import touch foo - svn import -m 'import for git svn' . "$svnrepo" >/dev/null + svn_cmd import -m 'import for git svn' . "$svnrepo" >/dev/null cd .. rm -rf import diff --git a/t/t9124-git-svn-dcommit-auto-props.sh b/t/t9124-git-svn-dcommit-auto-props.sh index 263dbf5fc2..d6b076f6b7 100755 --- a/t/t9124-git-svn-dcommit-auto-props.sh +++ b/t/t9124-git-svn-dcommit-auto-props.sh @@ -21,7 +21,7 @@ test_expect_success 'initialize git svn' ' ( cd import && echo foo >foo && - svn import -m "import for git svn" . "$svnrepo" + svn_cmd import -m "import for git svn" . "$svnrepo" ) && rm -rf import && git svn init "$svnrepo" @@ -61,23 +61,23 @@ test_expect_success 'check resulting svn repository' ' ( mkdir work && cd work && - svn co "$svnrepo" && + svn_cmd co "$svnrepo" && cd svnrepo && # Check properties from first commit. - test "x$(svn propget svn:executable exec1.sh)" = "x*" && - test "x$(svn propget svn:mime-type exec1.sh)" = \ + test "x$(svn_cmd propget svn:executable exec1.sh)" = "x*" && + test "x$(svn_cmd propget svn:mime-type exec1.sh)" = \ "xapplication/x-shellscript" && - test "x$(svn propget svn:mime-type hello.txt)" = "xtext/plain" && - test "x$(svn propget svn:eol-style hello.txt)" = "xnative" && - test "x$(svn propget svn:mime-type bar)" = "x" && + test "x$(svn_cmd propget svn:mime-type hello.txt)" = "xtext/plain" && + test "x$(svn_cmd propget svn:eol-style hello.txt)" = "xnative" && + test "x$(svn_cmd propget svn:mime-type bar)" = "x" && # Check properties from second commit. - test "x$(svn propget svn:executable exec2.sh)" = "x*" && - test "x$(svn propget svn:mime-type exec2.sh)" = "x" && - test "x$(svn propget svn:mime-type world.txt)" = "x" && - test "x$(svn propget svn:eol-style world.txt)" = "x" && - test "x$(svn propget svn:mime-type zot)" = "x" + test "x$(svn_cmd propget svn:executable exec2.sh)" = "x*" && + test "x$(svn_cmd propget svn:mime-type exec2.sh)" = "x" && + test "x$(svn_cmd propget svn:mime-type world.txt)" = "x" && + test "x$(svn_cmd propget svn:eol-style world.txt)" = "x" && + test "x$(svn_cmd propget svn:mime-type zot)" = "x" ) ' @@ -89,12 +89,12 @@ test_expect_success 'check renamed file' ' git svn dcommit --config-dir=user && ( cd work/svnrepo && - svn up && + svn_cmd up && test ! -e foo && test -e foo.sh && - test "x$(svn propget svn:mime-type foo.sh)" = \ + test "x$(svn_cmd propget svn:mime-type foo.sh)" = \ "xapplication/x-shellscript" && - test "x$(svn propget svn:eol-style foo.sh)" = "xLF" + test "x$(svn_cmd propget svn:eol-style foo.sh)" = "xLF" ) ' diff --git a/t/t9125-git-svn-multi-glob-branch-names.sh b/t/t9125-git-svn-multi-glob-branch-names.sh index 475c751c1c..c19418614f 100755 --- a/t/t9125-git-svn-multi-glob-branch-names.sh +++ b/t/t9125-git-svn-multi-glob-branch-names.sh @@ -8,11 +8,11 @@ test_expect_success 'setup svnrepo' ' mkdir project project/trunk project/branches \ project/branches/v14.1 project/tags && echo foo > project/trunk/foo && - svn import -m "$test_description" project "$svnrepo/project" && + svn_cmd import -m "$test_description" project "$svnrepo/project" && rm -rf project && - svn cp -m "fun" "$svnrepo/project/trunk" \ + svn_cmd cp -m "fun" "$svnrepo/project/trunk" \ "$svnrepo/project/branches/v14.1/beta" && - svn cp -m "more fun!" "$svnrepo/project/branches/v14.1/beta" \ + svn_cmd cp -m "more fun!" "$svnrepo/project/branches/v14.1/beta" \ "$svnrepo/project/branches/v14.1/gold" ' diff --git a/t/t9127-git-svn-partial-rebuild.sh b/t/t9127-git-svn-partial-rebuild.sh index 87696a92dc..4aab8ecc14 100755 --- a/t/t9127-git-svn-partial-rebuild.sh +++ b/t/t9127-git-svn-partial-rebuild.sh @@ -14,21 +14,21 @@ test_expect_success 'initialize svnrepo' ' cd trunk && echo foo > foo && cd .. && - svn import -m "import for git-svn" . "$svnrepo" >/dev/null && - svn copy "$svnrepo"/trunk "$svnrepo"/branches/a \ + svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null && + svn_cmd copy "$svnrepo"/trunk "$svnrepo"/branches/a \ -m "created branch a" && cd .. && rm -rf import && - svn co "$svnrepo"/trunk trunk && + svn_cmd co "$svnrepo"/trunk trunk && cd trunk && echo bar >> foo && - svn ci -m "updated trunk" && + svn_cmd ci -m "updated trunk" && cd .. && - svn co "$svnrepo"/branches/a a && + svn_cmd co "$svnrepo"/branches/a a && cd a && echo baz >> a && - svn add a && - svn ci -m "updated a" && + svn_cmd add a && + svn_cmd ci -m "updated a" && cd .. && git svn init --stdlayout "$svnrepo" ) diff --git a/t/t9128-git-svn-cmd-branch.sh b/t/t9128-git-svn-cmd-branch.sh index 252daa7e1a..807e494a3a 100755 --- a/t/t9128-git-svn-cmd-branch.sh +++ b/t/t9128-git-svn-cmd-branch.sh @@ -14,13 +14,13 @@ test_expect_success 'initialize svnrepo' ' cd trunk && echo foo > foo && cd .. && - svn import -m "import for git-svn" . "$svnrepo" >/dev/null && + svn_cmd import -m "import for git-svn" . "$svnrepo" >/dev/null && cd .. && rm -rf import && - svn co "$svnrepo"/trunk trunk && + svn_cmd co "$svnrepo"/trunk trunk && cd trunk && echo bar >> foo && - svn ci -m "updated trunk" && + svn_cmd ci -m "updated trunk" && cd .. && rm -rf trunk ) @@ -57,14 +57,14 @@ test_expect_success 'git svn branch tests' ' ' test_expect_success 'branch uses correct svn-remote' ' - (svn co "$svnrepo" svn && + (svn_cmd co "$svnrepo" svn && cd svn && mkdir mirror && - svn add mirror && - svn copy trunk mirror/ && - svn copy tags mirror/ && - svn copy branches mirror/ && - svn ci -m "made mirror" ) && + svn_cmd add mirror && + svn_cmd copy trunk mirror/ && + svn_cmd copy tags mirror/ && + svn_cmd copy branches mirror/ && + svn_cmd ci -m "made mirror" ) && rm -rf svn && git svn init -s -R mirror --prefix=mirror/ "$svnrepo"/mirror && git svn fetch -R mirror && diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index 9c7b1ad18b..b9224bdb20 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -29,16 +29,16 @@ compare_svn_head_with () { test_cmp current "$1" } -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "$H setup" ' mkdir $H && - svn import -m "$H test" $H "$svnrepo"/$H && + svn_cmd import -m "$H test" $H "$svnrepo"/$H && git svn clone "$svnrepo"/$H $H ' done -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "$H commit on git side" ' ( @@ -55,7 +55,7 @@ do ' done -for H in ISO-8859-1 EUCJP ISO-2022-JP +for H in ISO8859-1 eucJP ISO-2022-JP do test_expect_success "$H dcommit to svn" ' ( @@ -70,24 +70,26 @@ do done if locale -a |grep -q en_US.utf8; then - test_expect_success 'ISO-8859-1 should match UTF-8 in svn' ' + test_set_prereq UTF8 +else + say "UTF-8 locale not available, test skipped" +fi + +test_expect_success UTF8 'ISO-8859-1 should match UTF-8 in svn' ' ( - cd ISO-8859-1 && + cd ISO8859-1 && compare_svn_head_with "$TEST_DIRECTORY"/t3900/1-UTF-8.txt ) - ' +' - for H in EUCJP ISO-2022-JP - do - test_expect_success '$H should match UTF-8 in svn' ' +for H in eucJP ISO-2022-JP +do + test_expect_success UTF8 "$H should match UTF-8 in svn" ' ( cd $H && compare_svn_head_with "$TEST_DIRECTORY"/t3900/2-UTF-8.txt ) - ' - done -else - say "UTF-8 locale not available, test skipped" -fi + ' +done test_done diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index b8fb277562..f5abdb3c7f 100755 --- a/t/t9130-git-svn-authors-file.sh +++ b/t/t9130-git-svn-authors-file.sh @@ -15,7 +15,7 @@ EOF test_expect_success 'setup svnrepo' ' for i in aa bb cc dd do - svn mkdir -m $i --username $i "$svnrepo"/$i + svn_cmd mkdir -m $i --username $i "$svnrepo"/$i done ' @@ -52,13 +52,13 @@ test_expect_success 'continues to import once authors have been added' ' ' test_expect_success 'authors-file against globs' ' - svn mkdir -m globs --username aa \ + svn_cmd 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" + svn_cmd mkdir -m "$branch" --username $i "$svnrepo/$branch" done ' diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh index 8f35e294aa..9a24a65b64 100755 --- a/t/t9131-git-svn-empty-symlink.sh +++ b/t/t9131-git-svn-empty-symlink.sh @@ -88,7 +88,7 @@ test_expect_success 'enable broken symlink workaround' \ 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 SYMLINKS '"bar" becomes a symlink' 'test -L x/bar' test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y' diff --git a/t/t9132-git-svn-broken-symlink.sh b/t/t9132-git-svn-broken-symlink.sh index b8de59e493..6c4c90b036 100755 --- a/t/t9132-git-svn-broken-symlink.sh +++ b/t/t9132-git-svn-broken-symlink.sh @@ -85,7 +85,7 @@ 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_expect_success SYMLINKS '"bar" is a symlink that points to "asdf"' ' test -L x/bar && (cd x && test xasdf = x"`git cat-file blob HEAD:bar`") ' @@ -94,7 +94,7 @@ test_expect_success 'get "bar" => symlink fix from svn' ' (cd x && git svn rebase) ' -test_expect_success '"bar" remains a proper symlink' ' +test_expect_success SYMLINKS '"bar" remains a proper symlink' ' test -L x/bar && (cd x && test xdoink = x"`git cat-file blob HEAD:bar`") ' diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh index 893f57ef73..f3c30e63b7 100755 --- a/t/t9133-git-svn-nested-git-repo.sh +++ b/t/t9133-git-svn-nested-git-repo.sh @@ -7,19 +7,19 @@ 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 && + svn_cmd 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 && + svn_cmd add .git a && + svn_cmd commit -m "create a nested git repo" && + svn_cmd up && echo hi >> .git/a && - svn commit -m "modify .git/a" && - svn up + svn_cmd commit -m "modify .git/a" && + svn_cmd up ) ' @@ -33,9 +33,9 @@ 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" + svn_cmd commit -m "SVN-side change outside of .git" && + svn_cmd up && + svn_cmd log -v | fgrep "SVN-side change outside of .git" ) ' @@ -56,10 +56,10 @@ test_expect_success 'SVN-side change inside of .git' ' 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" + svn_cmd add --force .git && + svn_cmd commit -m "SVN-side change inside of .git" && + svn_cmd up && + svn_cmd log -v | fgrep "SVN-side change inside of .git" ) ' @@ -80,9 +80,9 @@ test_expect_success 'SVN-side change in and out of .git' ' 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" + svn_cmd commit -m "SVN-side change in and out of .git" && + svn_cmd up && + svn_cmd log -v | fgrep "SVN-side change in and out of .git" ) ' diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh index c4b5b8bcf7..09ff10cd9b 100755 --- a/t/t9134-git-svn-ignore-paths.sh +++ b/t/t9134-git-svn-ignore-paths.sh @@ -8,19 +8,19 @@ test_description='git svn property tests' . ./lib-git-svn.sh test_expect_success 'setup test repository' ' - svn co "$svnrepo" s && + svn_cmd 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 && + svn_cmd add qqq && + svn_cmd add www && + svn_cmd commit -m "create some files" && + svn_cmd up && echo hi >> www/test_www.txt && - svn commit -m "modify www/test_www.txt" && - svn up + svn_cmd commit -m "modify www/test_www.txt" && + svn_cmd up ) ' @@ -31,19 +31,46 @@ test_expect_success 'clone an SVN repository with ignored www directory' ' test_cmp expect expect2 ' +test_expect_success 'init+fetch an SVN repository with ignored www directory' ' + git svn init "$svnrepo" c && + ( cd c && git svn fetch --ignore-paths="^www" ) && + rm expect2 && + echo test_qqq > expect && + for i in c/*/*.txt; do cat $i >> expect2; done && + test_cmp expect expect2 +' + +test_expect_success 'verify ignore-paths config saved by clone' ' + ( + cd g && + git config --get svn-remote.svn.ignore-paths | fgrep "www" + ) +' + 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" + svn_cmd commit -m "SVN-side change outside of www" && + svn_cmd up && + svn_cmd log -v | fgrep "SVN-side change outside of www" ) ' -test_expect_success 'update git svn-cloned repo' ' +test_expect_success 'update git svn-cloned repo (config ignore)' ' ( cd g && + git svn rebase && + 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 'update git svn-cloned repo (option ignore)' ' + ( + cd c && git svn rebase --ignore-paths="^www" && printf "test_qqq\nb\n" > expect && for i in */*.txt; do cat $i >> expect2; done && @@ -56,15 +83,26 @@ 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" + svn_cmd commit -m "SVN-side change inside of www/test_www.txt" && + svn_cmd up && + svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt" ) ' -test_expect_success 'update git svn-cloned repo' ' +test_expect_success 'update git svn-cloned repo (config ignore)' ' ( cd g && + git svn rebase && + 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 'update git svn-cloned repo (option ignore)' ' + ( + cd c && git svn rebase --ignore-paths="^www" && printf "test_qqq\nb\n" > expect && for i in */*.txt; do cat $i >> expect2; done && @@ -78,15 +116,26 @@ 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" + svn_cmd commit -m "SVN-side change in and out of ignored www" && + svn_cmd up && + svn_cmd log -v | fgrep "SVN-side change in and out of ignored www" ) ' -test_expect_success 'update git svn-cloned repo again' ' +test_expect_success 'update git svn-cloned repo again (config ignore)' ' ( cd g && + git svn rebase && + printf "test_qqq\nb\nygg\n" > expect && + for i in */*.txt; do cat $i >> expect2; done && + test_cmp expect2 expect && + rm expect expect2 + ) +' + +test_expect_success 'update git svn-cloned repo again (option ignore)' ' + ( + cd c && git svn rebase --ignore-paths="^www" && printf "test_qqq\nb\nygg\n" > expect && for i in */*.txt; do cat $i >> expect2; done && diff --git a/t/t9106-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh index fd185011b7..636ca0abb9 100755 --- a/t/t9106-git-svn-dcommit-clobber-series.sh +++ b/t/t9137-git-svn-dcommit-clobber-series.sh @@ -8,7 +8,7 @@ test_expect_success 'initialize repo' ' mkdir import && cd import && awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file - svn import -m "initial" . "$svnrepo" && + svn_cmd import -m "initial" . "$svnrepo" && cd .. && git svn init "$svnrepo" && git svn fetch && @@ -18,14 +18,14 @@ test_expect_success 'initialize repo' ' test_expect_success '(supposedly) non-conflicting change from SVN' ' test x"`sed -n -e 58p < file`" = x58 && test x"`sed -n -e 61p < file`" = x61 && - svn co "$svnrepo" tmp && + svn_cmd co "$svnrepo" tmp && cd tmp && perl -i.bak -p -e "s/^58$/5588/" file && perl -i.bak -p -e "s/^61$/6611/" file && poke file && test x"`sed -n -e 58p < file`" = x5588 && test x"`sed -n -e 61p < file`" = x6611 && - svn commit -m "58 => 5588, 61 => 6611" && + svn_cmd commit -m "58 => 5588, 61 => 6611" && cd .. ' @@ -46,7 +46,7 @@ test_expect_success 'change file but in unrelated area' " test x\"\`sed -n -e 7p < file\`\" = x7777 && git commit -m '4 => 4444, 7 => 7777' file && git svn dcommit && - svn up tmp && + svn_cmd up tmp && cd tmp && test x\"\`sed -n -e 4p < file\`\" = x4444 && test x\"\`sed -n -e 7p < file\`\" = x7777 && diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh new file mode 100755 index 0000000000..a4b00f2a3f --- /dev/null +++ b/t/t9138-git-svn-authors-prog.sh @@ -0,0 +1,69 @@ +#!/bin/sh +# +# Copyright (c) 2009 Eric Wong, Mark Lodato +# + +test_description='git svn authors prog tests' + +. ./lib-git-svn.sh + +cat > svn-authors-prog <<'EOF' +#!/usr/bin/perl +$_ = shift; +if (s/-sub$//) { + print "$_ <$_\@sub.example.com>\n"; +} +else { + print "$_ <$_\@example.com>\n"; +} +EOF +chmod +x svn-authors-prog + +cat > svn-authors <<'EOF' +ff = FFFFFFF FFFFFFF <fFf@other.example.com> +EOF + +test_expect_success 'setup svnrepo' ' + for i in aa bb cc-sub dd-sub ee-foo ff + do + svn mkdir -m $i --username $i "$svnrepo"/$i + done + ' + +test_expect_success 'import authors with prog and file' ' + git svn clone --authors-prog=./svn-authors-prog \ + --authors-file=svn-authors "$svnrepo" x + ' + +test_expect_success 'imported 6 revisions successfully' ' + ( + cd x + test "`git rev-list refs/remotes/git-svn | wc -l`" -eq 6 + ) + ' + +test_expect_success 'authors-prog ran correctly' ' + ( + cd x + git rev-list -1 --pretty=raw refs/remotes/git-svn~1 | \ + grep "^author ee-foo <ee-foo@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~2 | \ + grep "^author dd <dd@sub\.example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~3 | \ + grep "^author cc <cc@sub\.example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~4 | \ + grep "^author bb <bb@example\.com> " && + git rev-list -1 --pretty=raw refs/remotes/git-svn~5 | \ + grep "^author aa <aa@example\.com> " + ) + ' + +test_expect_success 'authors-file overrode authors-prog' ' + ( + cd x + git rev-list -1 --pretty=raw refs/remotes/git-svn | \ + grep "^author FFFFFFF FFFFFFF <fFf@other\.example\.com> " + ) + ' + +test_done diff --git a/t/t9139-git-svn-non-utf8-commitencoding.sh b/t/t9139-git-svn-non-utf8-commitencoding.sh new file mode 100755 index 0000000000..f337959ccc --- /dev/null +++ b/t/t9139-git-svn-non-utf8-commitencoding.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# +# Copyright (c) 2009 Eric Wong + +test_description='git svn refuses to dcommit non-UTF8 messages' + +. ./lib-git-svn.sh + +# ISO-2022-JP can pass for valid UTF-8, so skipping that in this test + +for H in ISO8859-1 eucJP +do + test_expect_success "$H setup" ' + mkdir $H && + svn_cmd import -m "$H test" $H "$svnrepo"/$H && + git svn clone "$svnrepo"/$H $H + ' +done + +for H in ISO8859-1 eucJP +do + test_expect_success "$H commit on git side" ' + ( + cd $H && + git config i18n.commitencoding $H && + git checkout -b t refs/remotes/git-svn && + echo $H >F && + git add F && + git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt && + E=$(git cat-file commit HEAD | sed -ne "s/^encoding //p") && + test "z$E" = "z$H" + ) + ' +done + +for H in ISO8859-1 eucJP +do + test_expect_success "$H dcommit to svn" ' + ( + cd $H && + git config --unset i18n.commitencoding && + ! git svn dcommit + ) + ' +done + +test_done diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 245a7c3662..ef1f8d22f6 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -6,12 +6,16 @@ test_description='Test export of commits to CVS' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git cvsexportcommit tests, perl not available' + test_done +fi + cvs >/dev/null 2>&1 if test $? -ne 1 then - test_expect_success 'skipping git cvsexportcommit tests, cvs not found' : + say 'skipping git cvsexportcommit tests, cvs not found' test_done - exit fi CVSROOT=$(pwd)/cvsroot @@ -225,11 +229,12 @@ test_expect_success \ test_must_fail git cvsexportcommit -c $id )' -case "$(git config --bool core.filemode)" in -false) - ;; -*) -test_expect_success \ +if ! test "$(git config --bool core.filemode)" = false +then + test_set_prereq FILEMODE +fi + +test_expect_success FILEMODE \ 'Retain execute bit' \ 'mkdir G && echo executeon >G/on && @@ -243,8 +248,6 @@ test_expect_success \ test -x G/on && ! test -x G/off )' - ;; -esac test_expect_success '-w option should work with relative GIT_DIR' ' mkdir W && @@ -314,4 +317,22 @@ test_expect_success 'use the same checkout for Git and CVS' ' ' +test_expect_success 're-commit a removed filename which remains in CVS attic' ' + + (cd "$CVSWORK" && + echo >attic_gremlin && + cvs -Q add attic_gremlin && + cvs -Q ci -m "added attic_gremlin" && + rm attic_gremlin && + cvs -Q rm attic_gremlin && + cvs -Q ci -m "removed attic_gremlin") && + + echo > attic_gremlin && + git add attic_gremlin && + git commit -m "Added attic_gremlin" && + git cvsexportcommit -w "$CVSWORK" -c HEAD && + (cd "$CVSWORK"; cvs -Q update -d) && + test -f "$CVSWORK/attic_gremlin" +' + test_done diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh index 4a87f36258..8c8a9e63c2 100755 --- a/t/t9301-fast-export.sh +++ b/t/t9301-fast-export.sh @@ -8,6 +8,9 @@ test_description='git fast-export' test_expect_success 'setup' ' + echo break it > file0 && + git add file0 && + test_tick && echo Wohlauf > file && git add file && test_tick && @@ -57,15 +60,15 @@ test_expect_success 'fast-export master~2..master' ' (cd new && git fast-import && test $MASTER != $(git rev-parse --verify refs/heads/partial) && - git diff master..partial && - git diff master^..partial^ && + git diff --exit-code master partial && + git diff --exit-code master^ partial^ && test_must_fail git rev-parse partial~2) ' test_expect_success 'iso-8859-1' ' - git config i18n.commitencoding ISO-8859-1 && + git config i18n.commitencoding ISO8859-1 && # use author and committer name in ISO-8859-1 to match it. . "$TEST_DIRECTORY"/t3901-8859-1.txt && test_tick && diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 6a37f71d11..64f947d75b 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -10,17 +10,19 @@ cvs CLI client via git-cvsserver server' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git cvsserver tests, perl not available' + test_done +fi cvs >/dev/null 2>&1 if test $? -ne 1 then - test_expect_success 'skipping git-cvsserver tests, cvs not found' : + say 'skipping git-cvsserver tests, cvs not found' test_done - exit fi perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { - test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' : + say 'skipping git-cvsserver tests, Perl SQLite interface unavailable' test_done - exit } unset GIT_DIR GIT_CONFIG @@ -44,7 +46,7 @@ test_expect_success 'setup' ' git add secondrootfile && git commit -m "second root") && git pull secondroot master && - git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && + git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ' @@ -267,7 +269,7 @@ test_expect_success 'gitcvs.ext.dbname' \ rm -fr "$SERVERDIR" cd "$WORKDIR" && -git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && +git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" || exit 1 diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh index e27a1c5f85..aca40c1b1f 100755 --- a/t/t9401-git-cvsserver-crlf.sh +++ b/t/t9401-git-cvsserver-crlf.sh @@ -49,14 +49,17 @@ not_present() { cvs >/dev/null 2>&1 if test $? -ne 1 then - test_expect_success 'skipping git-cvsserver tests, cvs not found' : + say 'skipping git-cvsserver tests, cvs not found' + test_done +fi +if ! test_have_prereq PERL +then + say 'skipping git-cvsserver tests, perl not available' test_done - exit fi perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || { - test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' : + say 'skipping git-cvsserver tests, Perl SQLite interface unavailable' test_done - exit } unset GIT_DIR GIT_CONFIG @@ -84,7 +87,7 @@ test_expect_success 'setup' ' echo "subdir/file.h crlf" >> .gitattributes && git add .gitattributes textfile.c binfile.bin mixedUp.c subdir/* && git commit -q -m "First Commit" && - git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && + git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 && GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true && GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ' diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 6ed10d0933..d539619e89 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -63,20 +63,16 @@ gitweb_run () { # gitweb.log is left for debugging } -safe_chmod () { - chmod "$1" "$2" && - if [ "$(git config --get core.filemode)" = false ] - then - git update-index --chmod="$1" "$2" - fi -} - . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping gitweb tests, perl not available' + test_done +fi + perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || { - test_expect_success 'skipping gitweb tests, perl version is too old' : + say 'skipping gitweb tests, perl version is too old' test_done - exit } gitweb_init @@ -242,7 +238,7 @@ test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): mode change' \ - 'safe_chmod +x new_file && + 'test_chmod +x new_file && git commit -a -m "Mode changed." && gitweb_run "p=.git;a=commitdiff"' test_debug 'cat gitweb.log' @@ -254,7 +250,7 @@ test_expect_success \ gitweb_run "p=.git;a=commitdiff"' test_debug 'cat gitweb.log' -test_expect_success \ +test_expect_success SYMLINKS \ 'commitdiff(0): file to symlink' \ 'rm renamed_file && ln -s file renamed_file && @@ -281,7 +277,7 @@ test_debug 'cat gitweb.log' test_expect_success \ 'commitdiff(0): mode change and modified' \ 'echo "New line" >> file2 && - safe_chmod +x file2 && + test_chmod +x file2 && git commit -a -m "Mode change and modification." && gitweb_run "p=.git;a=commitdiff"' test_debug 'cat gitweb.log' @@ -308,7 +304,7 @@ test_expect_success \ 'commitdiff(0): renamed, mode change and modified' \ 'git mv file3 file2 && echo "Propter nomen suum." >> file2 && - safe_chmod +x file2 && + test_chmod +x file2 && git commit -a -m "File rename, mode change and modification." && gitweb_run "p=.git;a=commitdiff"' test_debug 'cat gitweb.log' @@ -316,7 +312,7 @@ test_debug 'cat gitweb.log' # ---------------------------------------------------------------------- # commitdiff testing (taken from t4114-apply-typechange.sh) -test_expect_success 'setup typechange commits' ' +test_expect_success SYMLINKS 'setup typechange commits' ' echo "hello world" > foo && echo "hi planet" > bar && git update-index --add foo bar && @@ -425,10 +421,15 @@ test_expect_success \ git add 03-new && git mv 04-rename-from 04-rename-to && echo "Changed" >> 04-rename-to && - safe_chmod +x 05-mode-change && - rm -f 06-file-or-symlink && ln -s 01-change 06-file-or-symlink && + test_chmod +x 05-mode-change && + rm -f 06-file-or-symlink && + if test_have_prereq SYMLINKS; then + ln -s 01-change 06-file-or-symlink + else + printf %s 01-change > 06-file-or-symlink + fi && echo "Changed and have mode changed" > 07-change-mode-change && - safe_chmod +x 07-change-mode-change && + test_chmod +x 07-change-mode-change && git commit -a -m "Large commit" && git checkout master' @@ -589,7 +590,7 @@ test_expect_success \ echo "ISO-8859-1" >> file && git add file && git config i18n.commitencoding ISO-8859-1 && - git commit -F "$TEST_DIRECTORY"/t3900/ISO-8859-1.txt && + git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt && git config --unset i18n.commitencoding && gitweb_run "p=.git;a=commit"' test_debug 'cat gitweb.log' diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index d2379e7f62..4322a0c1ed 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -3,6 +3,11 @@ test_description='git cvsimport basic tests' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping git cvsimport tests, perl not available' + test_done +fi + CVSROOT=$(pwd)/cvsroot export CVSROOT unset CVS_SERVER @@ -14,7 +19,6 @@ if ! type cvs >/dev/null 2>&1 then say 'skipping cvsimport tests, cvs not found' test_done - exit fi cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'` @@ -24,12 +28,10 @@ case "$cvsps_version" in '') say 'skipping cvsimport tests, cvsps not found' test_done - exit ;; *) say 'skipping cvsimport tests, unsupported cvsps version' test_done - exit ;; esac diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh index b81d5dfc34..4eb7d3f7f0 100755 --- a/t/t9700-perl-git.sh +++ b/t/t9700-perl-git.sh @@ -6,8 +6,13 @@ test_description='perl interface (Git.pm)' . ./test-lib.sh +if ! test_have_prereq PERL; then + say 'skipping perl interface tests, perl not available' + test_done +fi + perl -MTest::More -e 0 2>/dev/null || { - say_color skip "Perl Test::More unavailable, skipping test" + say "Perl Test::More unavailable, skipping test" test_done } @@ -24,6 +29,10 @@ test_expect_success \ git add . && git commit -m "first commit" && + echo "new file in subdir 2" > directory2/file2 && + git add . && + git commit -m "commit in directory2" && + echo "changed file 1" > file1 && git commit -a -m "second commit" && diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 697daf3ffd..6c70aec020 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -86,15 +86,22 @@ close TEMPFILE; unlink $tmpfile; # paths -is($r->repo_path, "./.git", "repo_path"); +is($r->repo_path, $abs_repo_dir . "/.git", "repo_path"); is($r->wc_path, $abs_repo_dir . "/", "wc_path"); is($r->wc_subdir, "", "wc_subdir initial"); $r->wc_chdir("directory1"); is($r->wc_subdir, "directory1", "wc_subdir after wc_chdir"); -TODO: { - local $TODO = "commands do not work after wc_chdir"; - # Failure output is active even in non-verbose mode and thus - # annoying. Hence we skip these tests as long as they fail. - todo_skip 'config after wc_chdir', 1; - is($r->config("color.string"), "value", "config after wc_chdir"); -} +is($r->config("test.string"), "value", "config after wc_chdir"); + +# Object generation in sub directory +chdir("directory2"); +my $r2 = Git->repository(); +is($r2->repo_path, $abs_repo_dir . "/.git", "repo_path (2)"); +is($r2->wc_path, $abs_repo_dir . "/", "wc_path (2)"); +is($r2->wc_subdir, "directory2/", "wc_subdir initial (2)"); + +# commands in sub directory +my $last_commit = $r2->command_oneline(qw(rev-parse --verify HEAD)); +like($last_commit, qr/^[0-9a-fA-F]{40}$/, 'rev-parse returned hash'); +my $dir_commit = $r2->command_oneline('log', '-n1', '--pretty=format:%H', '.'); +isnt($last_commit, $dir_commit, 'log . does not show last commit'); diff --git a/t/test-lib.sh b/t/test-lib.sh index 59d82d25e9..5fdc5d94a2 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -3,6 +3,22 @@ # Copyright (c) 2005 Junio C Hamano # +# if --tee was passed, write the output not only to the terminal, but +# additionally to the file test-results/$BASENAME.out, too. +case "$GIT_TEST_TEE_STARTED, $* " in +done,*) + # do not redirect again + ;; +*' --tee '*|*' --va'*) + mkdir -p test-results + BASE=test-results/$(basename "$0" .sh) + (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1; + echo $? > $BASE.exit) | tee $BASE.out + test "$(cat $BASE.exit)" = 0 + exit + ;; +esac + # Keep the original TERM for say_color ORIGINAL_TERM=$TERM @@ -94,8 +110,12 @@ do --no-python) # noop now... shift ;; + --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) + valgrind=t; verbose=t; shift ;; + --tee) + shift ;; # was handled already *) - break ;; + echo "error: unknown test option '$1'" >&2; exit 1 ;; esac done @@ -127,7 +147,7 @@ fi error () { say_color error "error: $*" - trap - EXIT + GIT_EXIT_OK=t exit 1 } @@ -159,10 +179,17 @@ test_broken=0 test_success=0 die () { - echo >&5 "FATAL: Unexpected exit with code $?" - exit 1 + code=$? + if test -n "$GIT_EXIT_OK" + then + exit $code + else + echo >&5 "FATAL: Unexpected exit with code $code" + exit 1 + fi } +GIT_EXIT_OK= trap 'die' EXIT # The semantics of the editor variables are that of invoking @@ -218,32 +245,62 @@ test_merge () { git tag "$1" } +# This function helps systems where core.filemode=false is set. +# Use it instead of plain 'chmod +x' to set or unset the executable bit +# of a file in the working directory and add it to the index. + +test_chmod () { + chmod "$@" && + git update-index --add "--chmod=$@" +} + +# Use test_set_prereq to tell that a particular prerequisite is available. +# The prerequisite can later be checked for in two ways: +# +# - Explicitly using test_have_prereq. +# +# - Implicitly by specifying the prerequisite tag in the calls to +# test_expect_{success,failure,code}. +# +# The single parameter is the prerequisite tag (a simple word, in all +# capital letters by convention). + +test_set_prereq () { + satisfied="$satisfied$1 " +} +satisfied=" " + +test_have_prereq () { + case $satisfied in + *" $1 "*) + : yes, have it ;; + *) + ! : nope ;; + esac +} + # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. test_ok_ () { - test_count=$(expr "$test_count" + 1) - test_success=$(expr "$test_success" + 1) + test_success=$(($test_success + 1)) say_color "" " ok $test_count: $@" } test_failure_ () { - test_count=$(expr "$test_count" + 1) - test_failure=$(expr "$test_failure" + 1); + test_failure=$(($test_failure + 1)) say_color error "FAIL $test_count: $1" shift echo "$@" | sed -e 's/^/ /' - test "$immediate" = "" || { trap - EXIT; exit 1; } + test "$immediate" = "" || { GIT_EXIT_OK=t; exit 1; } } test_known_broken_ok_ () { - test_count=$(expr "$test_count" + 1) test_fixed=$(($test_fixed+1)) say_color "" " FIXED $test_count: $@" } test_known_broken_failure_ () { - test_count=$(expr "$test_count" + 1) test_broken=$(($test_broken+1)) say_color skip " still broken $test_count: $@" } @@ -259,20 +316,23 @@ test_run_ () { } test_skip () { - this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$') - this_test="$this_test.$(expr "$test_count" + 1)" + test_count=$(($test_count+1)) to_skip= for skp in $GIT_SKIP_TESTS do - case "$this_test" in + case $this_test.$test_count in $skp) to_skip=t esac done + if test -z "$to_skip" && test -n "$prereq" && + ! test_have_prereq "$prereq" + then + to_skip=t + fi case "$to_skip" in t) say_color skip >&3 "skipping test: $@" - test_count=$(expr "$test_count" + 1) say_color skip "skip $test_count: $1" : true ;; @@ -283,8 +343,9 @@ test_skip () { } test_expect_failure () { + test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || - error "bug in the test script: not 2 parameters to test-expect-failure" + error "bug in the test script: not 2 or 3 parameters to test-expect-failure" if ! test_skip "$@" then say >&3 "checking known breakage: $2" @@ -293,15 +354,16 @@ test_expect_failure () { then test_known_broken_ok_ "$1" else - test_known_broken_failure_ "$1" + test_known_broken_failure_ "$1" fi fi echo >&3 "" } test_expect_success () { + test "$#" = 3 && { prereq=$1; shift; } || prereq= test "$#" = 2 || - error "bug in the test script: not 2 parameters to test-expect-success" + error "bug in the test script: not 2 or 3 parameters to test-expect-success" if ! test_skip "$@" then say >&3 "expecting success: $2" @@ -317,8 +379,9 @@ test_expect_success () { } test_expect_code () { + test "$#" = 4 && { prereq=$1; shift; } || prereq= test "$#" = 3 || - error "bug in the test script: not 3 parameters to test-expect-code" + error "bug in the test script: not 3 or 4 parameters to test-expect-code" if ! test_skip "$@" then say >&3 "expecting exit code $1: $3" @@ -342,15 +405,16 @@ test_expect_code () { # Usage: test_external description command arguments... # Example: test_external 'Perl API' perl ../path/to/test.pl test_external () { - test "$#" -eq 3 || - error >&5 "bug in the test script: not 3 parameters to test_external" + test "$#" = 4 && { prereq=$1; shift; } || prereq= + test "$#" = 3 || + error >&5 "bug in the test script: not 3 or 4 parameters to test_external" descr="$1" shift if ! test_skip "$descr" "$@" then # Announce the script to reduce confusion about the # test output that follows. - say_color "" " run $(expr "$test_count" + 1): $descr ($*)" + say_color "" " run $test_count: $descr ($*)" # Run command; redirect its stderr to &4 as in # test_run_, but keep its stdout on our stdout even in # non-verbose mode. @@ -434,17 +498,17 @@ test_create_repo () { repo="$1" mkdir -p "$repo" cd "$repo" || error "Cannot setup test environment" - "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >&3 2>&4 || + "$GIT_EXEC_PATH/git-init" "--template=$TEST_DIRECTORY/../templates/blt/" >&3 2>&4 || error "cannot run git init -- have you built things yet?" mv .git/hooks .git/hooks-disabled cd "$owd" } test_done () { - trap - EXIT + GIT_EXIT_OK=t test_results_dir="$TEST_DIRECTORY/test-results" mkdir -p "$test_results_dir" - test_results_path="$test_results_dir/${0%-*}-$$" + test_results_path="$test_results_dir/${0%.sh}-$$" echo "total $test_count" >> $test_results_path echo "success $test_success" >> $test_results_path @@ -484,8 +548,81 @@ test_done () { # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in 'trash directory' subdirectory. TEST_DIRECTORY=$(pwd) -PATH=$TEST_DIRECTORY/..:$PATH -GIT_EXEC_PATH=$(pwd)/.. +if test -z "$valgrind" +then + if test -z "$GIT_TEST_INSTALLED" + then + PATH=$TEST_DIRECTORY/..:$PATH + GIT_EXEC_PATH=$TEST_DIRECTORY/.. + else + GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path) || + error "Cannot run git from $GIT_TEST_INSTALLED." + PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH + GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH} + fi +else + make_symlink () { + test -h "$2" && + test "$1" = "$(readlink "$2")" || { + # be super paranoid + if mkdir "$2".lock + then + rm -f "$2" && + ln -s "$1" "$2" && + rm -r "$2".lock + else + while test -d "$2".lock + do + say "Waiting for lock on $2." + sleep 1 + done + fi + } + } + + make_valgrind_symlink () { + # handle only executables + test -x "$1" || return + + base=$(basename "$1") + symlink_target=$TEST_DIRECTORY/../$base + # do not override scripts + if test -x "$symlink_target" && + test ! -d "$symlink_target" && + test "#!" != "$(head -c 2 < "$symlink_target")" + then + symlink_target=../valgrind.sh + fi + case "$base" in + *.sh|*.perl) + symlink_target=../unprocessed-script + esac + # create the link, or replace it if it is out of date + make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit + } + + # override all git executables in TEST_DIRECTORY/.. + GIT_VALGRIND=$TEST_DIRECTORY/valgrind + mkdir -p "$GIT_VALGRIND"/bin + for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-* + do + make_valgrind_symlink $file + done + OLDIFS=$IFS + IFS=: + for path in $PATH + do + ls "$path"/git-* 2> /dev/null | + while read file + do + make_valgrind_symlink "$file" + done + done + IFS=$OLDIFS + PATH=$GIT_VALGRIND/bin:$PATH + GIT_EXEC_PATH=$GIT_VALGRIND/bin + export GIT_VALGRIND +fi GIT_TEMPLATE_DIR=$(pwd)/../templates/blt unset GIT_CONFIG GIT_CONFIG_NOSYSTEM=1 @@ -510,7 +647,7 @@ fi test="trash directory.$(basename "$0" .sh)" test ! -z "$debug" || remove_trash="$TEST_DIRECTORY/$test" rm -fr "$test" || { - trap - EXIT + GIT_EXIT_OK=t echo >&5 "FATAL: Cannot prepare test area" exit 1 } @@ -520,7 +657,8 @@ test_create_repo "$test" # in subprocesses like git equals our $PWD (for pathname comparisons). cd -P "$test" || exit 1 -this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$') +this_test=${0##*/} +this_test=${this_test%%-*} for skp in $GIT_SKIP_TESTS do to_skip= @@ -538,3 +676,37 @@ do test_done esac done + +# Fix some commands on Windows +case $(uname -s) in +*MINGW*) + # Windows has its own (incompatible) sort and find + sort () { + /usr/bin/sort "$@" + } + find () { + /usr/bin/find "$@" + } + sum () { + md5sum "$@" + } + # git sees Windows-style pwd + pwd () { + builtin pwd -W + } + # no POSIX permissions + # backslashes in pathspec are converted to '/' + # exec does not inherit the PID + ;; +*) + test_set_prereq POSIXPERM + test_set_prereq BSLASHPSPEC + test_set_prereq EXECKEEPSPID + ;; +esac + +test -z "$NO_PERL" && test_set_prereq PERL + +# test whether the filesystem supports symbolic links +ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS +rm -f y diff --git a/t/valgrind/.gitignore b/t/valgrind/.gitignore new file mode 100644 index 0000000000..d4ae6676d1 --- /dev/null +++ b/t/valgrind/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/templates diff --git a/t/valgrind/analyze.sh b/t/valgrind/analyze.sh new file mode 100755 index 0000000000..d8105d9fab --- /dev/null +++ b/t/valgrind/analyze.sh @@ -0,0 +1,123 @@ +#!/bin/sh + +out_prefix=$(dirname "$0")/../test-results/valgrind.out +output= +count=0 +total_count=0 +missing_message= +new_line=' +' + +# start outputting the current valgrind error in $out_prefix.++$count, +# and the test case which failed in the corresponding .message file +start_output () { + test -z "$output" || return + + # progress + total_count=$(($total_count+1)) + test -t 2 && printf "\rFound %d errors" $total_count >&2 + + count=$(($count+1)) + output=$out_prefix.$count + : > $output + + echo "*** $1 ***" > $output.message +} + +finish_output () { + test ! -z "$output" || return + output= + + # if a test case has more than one valgrind error, we need to + # copy the last .message file to the previous errors + test -z "$missing_message" || { + while test $missing_message -lt $count + do + cp $out_prefix.$count.message \ + $out_prefix.$missing_message.message + missing_message=$(($missing_message+1)) + done + missing_message= + } +} + +# group the valgrind errors by backtrace +output_all () { + last_line= + j=0 + i=1 + while test $i -le $count + do + # output <number> <backtrace-in-one-line> + echo "$i $(tr '\n' ' ' < $out_prefix.$i)" + i=$(($i+1)) + done | + sort -t ' ' -k 2 | # order by <backtrace-in-one-line> + while read number line + do + # find duplicates, do not output backtrace twice + if test "$line" != "$last_line" + then + last_line=$line + j=$(($j+1)) + printf "\nValgrind error $j:\n\n" + cat $out_prefix.$number + printf "\nfound in:\n" + fi + # print the test case where this came from + printf "\n" + cat $out_prefix.$number.message + done +} + +handle_one () { + OLDIFS=$IFS + IFS="$new_line" + while read line + do + case "$line" in + # backtrace, possibly a new one + ==[0-9]*) + + # Does the current valgrind error have a message yet? + case "$output" in + *.message) + test -z "$missing_message" && + missing_message=$count + output= + esac + + start_output $(basename $1) + echo "$line" | + sed 's/==[0-9]*==/==valgrind==/' >> $output + ;; + # end of backtrace + '}') + test -z "$output" || { + echo "$line" >> $output + test $output = ${output%.message} && + output=$output.message + } + ;; + # end of test case + '') + finish_output + ;; + # normal line; if $output is set, print the line + *) + test -z "$output" || echo "$line" >> $output + ;; + esac + done < $1 + IFS=$OLDIFS + + # just to be safe + finish_output +} + +for test_script in "$(dirname "$0")"/../test-results/*.out +do + handle_one $test_script +done + +output_all diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp new file mode 100644 index 0000000000..9e013fa3b2 --- /dev/null +++ b/t/valgrind/default.supp @@ -0,0 +1,45 @@ +{ + ignore-zlib-errors-cond + Memcheck:Cond + obj:*libz.so* +} + +{ + ignore-zlib-errors-value8 + Memcheck:Value8 + obj:*libz.so* +} + +{ + ignore-zlib-errors-value4 + Memcheck:Value4 + obj:*libz.so* +} + +{ + ignore-ldso-cond + Memcheck:Cond + obj:*ld-*.so +} + +{ + ignore-ldso-addr8 + Memcheck:Addr8 + obj:*ld-*.so +} + +{ + ignore-ldso-addr4 + Memcheck:Addr4 + obj:*ld-*.so +} + +{ + writing-data-from-zlib-triggers-even-more-errors + Memcheck:Param + write(buf) + obj:/lib/ld-*.so + fun:write_in_full + fun:write_buffer + fun:write_loose_object +} diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh new file mode 100755 index 0000000000..582b4dca94 --- /dev/null +++ b/t/valgrind/valgrind.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +base=$(basename "$0") + +TRACK_ORIGINS= + +VALGRIND_VERSION=$(valgrind --version) +VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)') +VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)') +test 3 -gt "$VALGRIND_MAJOR" || +test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" || +TRACK_ORIGINS=--track-origins=yes + +exec valgrind -q --error-exitcode=126 \ + --leak-check=no \ + --suppressions="$GIT_VALGRIND/default.supp" \ + --gen-suppressions=all \ + $TRACK_ORIGINS \ + --log-fd=4 \ + --input-fd=4 \ + $GIT_VALGRIND_OPTIONS \ + "$GIT_VALGRIND"/../../"$base" "$@" diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index 0e49279c7f..b11ad6a6fb 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -7,7 +7,32 @@ # # To enable this hook, rename this file to "pre-commit". -if git-rev-parse --verify HEAD 2>/dev/null +# If you want to allow non-ascii filenames set this variable to true. +allownonascii=$(git config hooks.allownonascii) + +# Cross platform projects tend to avoid non-ascii filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + test "$(git diff --cached --name-only --diff-filter=A -z | + LC_ALL=C tr -d '[ -~]\0')" +then + echo "Error: Attempt to add a non-ascii filename." + echo + echo "This can cause problems if you want to work together" + echo "with people on other platforms than you." + echo + echo "To be portable it is adviseable to rename the file ..." + echo + echo "If you know what you are doing you can disable this" + echo "check using:" + echo + echo " git config hooks.allownonascii true" + echo + exit 1 +fi + +if git-rev-parse --verify HEAD >/dev/null 2>&1 then against=HEAD else diff --git a/templates/hooks--update.sample b/templates/hooks--update.sample index 93c605594f..fd63b2d662 100755 --- a/templates/hooks--update.sample +++ b/templates/hooks--update.sample @@ -13,9 +13,15 @@ # hooks.allowdeletetag # This boolean sets whether deleting tags will be allowed in the # repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. # hooks.allowdeletebranch # This boolean sets whether deleting branches will be allowed in the # repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. # # --- Command line @@ -39,18 +45,23 @@ fi # --- Config allowunannotated=$(git config --bool hooks.allowunannotated) allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) # check for no description projectdesc=$(sed -e '1q' "$GIT_DIR/description") -if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then +case "$projectdesc" in +"Unnamed repository"* | "") echo "*** Project description file hasn't been set" >&2 exit 1 -fi + ;; +esac # --- Check types # if $newrev is 0000...0000, it's a commit to delete a ref. -if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then newrev_type=delete else newrev_type=$(git-cat-file -t $newrev) @@ -75,9 +86,19 @@ case "$refname","$newrev_type" in ;; refs/tags/*,tag) # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi ;; refs/heads/*,commit) # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi ;; refs/heads/*,delete) # delete branch diff --git a/templates/this--description b/templates/this--description index c6f25e80b8..498b267a8c 100644 --- a/templates/this--description +++ b/templates/this--description @@ -1 +1 @@ -Unnamed repository; edit this file to name it for gitweb. +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test-chmtime.c b/test-chmtime.c index d5358cbaac..fe476cb618 100644 --- a/test-chmtime.c +++ b/test-chmtime.c @@ -87,6 +87,15 @@ int main(int argc, const char *argv[]) return -1; } +#ifdef WIN32 + if (!(sb.st_mode & S_IWUSR) && + chmod(argv[i], sb.st_mode | S_IWUSR)) { + fprintf(stderr, "Could not make user-writable %s: %s", + argv[i], strerror(errno)); + return -1; + } +#endif + utb.actime = sb.st_atime; utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; diff --git a/test-parse-options.c b/test-parse-options.c index 61d2c39814..a90bc3003d 100644 --- a/test-parse-options.c +++ b/test-parse-options.c @@ -7,6 +7,7 @@ static unsigned long timestamp; static int abbrev = 7; static int verbose = 0, dry_run = 0, quiet = 0; static char *string = NULL; +static char *file = NULL; int length_callback(const struct option *opt, const char *arg, int unset) { @@ -19,8 +20,15 @@ int length_callback(const struct option *opt, const char *arg, int unset) return 0; } +int number_callback(const struct option *opt, const char *arg, int unset) +{ + *(int *)opt->value = strtol(arg, NULL, 10); + return 0; +} + int main(int argc, const char **argv) { + const char *prefix = "prefix/"; const char *usage[] = { "test-parse-options <options>", NULL @@ -29,6 +37,7 @@ int main(int argc, const char **argv) OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"), OPT_BIT('4', "or4", &boolean, "bitwise-or boolean with ...0100", 4), + OPT_NEGBIT(0, "neg-or4", &boolean, "same as --no-or4", 4), OPT_GROUP(""), OPT_INTEGER('i', "integer", &integer, "get a integer"), OPT_INTEGER('j', NULL, &integer, "get a integer, too"), @@ -36,6 +45,7 @@ int main(int argc, const char **argv) OPT_DATE('t', NULL, ×tamp, "get timestamp of <time>"), OPT_CALLBACK('L', "length", &integer, "str", "get length of <str>", length_callback), + OPT_FILENAME('F', "file", &file, "set file to <FILE>"), OPT_GROUP("String options"), OPT_STRING('s', "string", &string, "string", "get a string"), OPT_STRING(0, "string2", &string, "str", "get another string"), @@ -45,6 +55,10 @@ int main(int argc, const char **argv) "set string to default", (unsigned long)"default"), OPT_GROUP("Magic arguments"), OPT_ARGUMENT("quux", "means --quux"), + OPT_NUMBER_CALLBACK(&integer, "set integer to NUM", + number_callback), + { OPTION_BOOLEAN, '+', NULL, &boolean, NULL, "same as -b", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH }, OPT_GROUP("Standard options"), OPT__ABBREV(&abbrev), OPT__VERBOSE(&verbose), @@ -54,7 +68,7 @@ int main(int argc, const char **argv) }; int i; - argc = parse_options(argc, argv, options, usage, 0); + argc = parse_options(argc, argv, prefix, options, usage, 0); printf("boolean: %d\n", boolean); printf("integer: %u\n", integer); @@ -64,6 +78,7 @@ int main(int argc, const char **argv) printf("verbose: %d\n", verbose); printf("quiet: %s\n", quiet ? "yes" : "no"); printf("dry run: %s\n", dry_run ? "yes" : "no"); + printf("file: %s\n", file ? file : "(not set)"); for (i = 0; i < argc; i++) printf("arg %02d: %s\n", i, argv[i]); diff --git a/transport.c b/transport.c index 9ae92cd39c..501a77b241 100644 --- a/transport.c +++ b/transport.c @@ -143,7 +143,7 @@ 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) +static struct ref *get_refs_via_rsync(struct transport *transport, int for_push) { struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT; struct ref dummy, *tail = &dummy; @@ -151,6 +151,9 @@ static struct ref *get_refs_via_rsync(struct transport *transport) const char *args[5]; int temp_dir_len; + if (for_push) + return NULL; + /* copy the refs to the temporary directory */ strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX")); @@ -429,16 +432,14 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons return !!err; } -static struct ref *get_refs_via_curl(struct transport *transport) +static struct ref *get_refs_via_curl(struct transport *transport, int for_push) { struct strbuf buffer = STRBUF_INIT; char *data, *start, *mid; char *ref_name; char *refs_url; int i = 0; - - struct active_request_slot *slot; - struct slot_results results; + int http_ret; struct ref *refs = NULL; struct ref *ref = NULL; @@ -446,6 +447,9 @@ static struct ref *get_refs_via_curl(struct transport *transport) struct walker *walker; + if (for_push) + return NULL; + if (!transport->data) transport->data = get_http_walker(transport->url, transport->remote); @@ -455,25 +459,16 @@ static struct ref *get_refs_via_curl(struct transport *transport) refs_url = xmalloc(strlen(transport->url) + 11); sprintf(refs_url, "%s/info/refs", transport->url); - slot = get_active_slot(); - slot->results = &results; - curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, refs_url); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); - - if (start_active_slot(slot)) { - run_active_slot(slot); - if (results.curl_result != CURLE_OK) { - strbuf_release(&buffer); - if (missing_target(&results)) - die("%s not found: did you run git update-server-info on the server?", refs_url); - else - die("%s download error - %s", refs_url, curl_errorstr); - } - } else { - strbuf_release(&buffer); - die("Unable to start HTTP request"); + http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE); + switch (http_ret) { + case HTTP_OK: + break; + case HTTP_MISSING_TARGET: + die("%s not found: did you run git update-server-info on the" + " server?", refs_url); + default: + http_error(refs_url, http_ret); + die("HTTP request failed"); } data = buffer.buf; @@ -513,6 +508,8 @@ static struct ref *get_refs_via_curl(struct transport *transport) free(ref); } + strbuf_release(&buffer); + free(refs_url); return refs; } @@ -532,12 +529,15 @@ struct bundle_transport_data { struct bundle_header header; }; -static struct ref *get_refs_from_bundle(struct transport *transport) +static struct ref *get_refs_from_bundle(struct transport *transport, int for_push) { struct bundle_transport_data *data = transport->data; struct ref *result = NULL; int i; + if (for_push) + return NULL; + if (data->fd > 0) close(data->fd); data->fd = read_bundle_header(transport->url, &data->header); @@ -578,6 +578,7 @@ struct git_transport_data { int fd[2]; const char *uploadpack; const char *receivepack; + struct extra_have_objects extra_have; }; static int set_git_option(struct transport *connection, @@ -609,20 +610,23 @@ static int set_git_option(struct transport *connection, return 1; } -static int connect_setup(struct transport *transport) +static int connect_setup(struct transport *transport, int for_push, int verbose) { struct git_transport_data *data = transport->data; - data->conn = git_connect(data->fd, transport->url, data->uploadpack, 0); + data->conn = git_connect(data->fd, transport->url, + for_push ? data->receivepack : data->uploadpack, + verbose ? CONNECT_VERBOSE : 0); return 0; } -static struct ref *get_refs_via_connect(struct transport *transport) +static struct ref *get_refs_via_connect(struct transport *transport, int for_push) { struct git_transport_data *data = transport->data; struct ref *refs; - connect_setup(transport); - get_remote_heads(data->fd[0], &refs, 0, NULL, 0, NULL); + connect_setup(transport, for_push, 0); + get_remote_heads(data->fd[0], &refs, 0, NULL, + for_push ? REF_NORMAL : 0, &data->extra_have); return refs; } @@ -654,7 +658,7 @@ static int fetch_refs_via_pack(struct transport *transport, origh[i] = heads[i] = xstrdup(to_fetch[i]->name); if (!data->conn) { - connect_setup(transport); + connect_setup(transport, 0, 0); get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL); } @@ -677,20 +681,216 @@ static int fetch_refs_via_pack(struct transport *transport, return (refs ? 0 : -1); } -static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) +static int refs_pushed(struct ref *ref) +{ + for (; ref; ref = ref->next) { + switch(ref->status) { + case REF_STATUS_NONE: + case REF_STATUS_UPTODATE: + break; + default: + return 1; + } + } + return 0; +} + +static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose) +{ + struct refspec rs; + + if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE) + return; + + rs.src = ref->name; + rs.dst = NULL; + + if (!remote_find_tracking(remote, &rs)) { + if (verbose) + fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst); + if (ref->deletion) { + delete_ref(rs.dst, NULL, 0); + } else + update_ref("update by push", rs.dst, + ref->new_sha1, NULL, 0, 0); + free(rs.dst); + } +} + +#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) + +static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg) +{ + fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary); + if (from) + fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name)); + else + fputs(prettify_refname(to->name), stderr); + if (msg) { + fputs(" (", stderr); + fputs(msg, stderr); + fputc(')', stderr); + } + fputc('\n', stderr); +} + +static const char *status_abbrev(unsigned char sha1[20]) +{ + return find_unique_abbrev(sha1, DEFAULT_ABBREV); +} + +static void print_ok_ref_status(struct ref *ref) +{ + if (ref->deletion) + print_ref_status('-', "[deleted]", ref, NULL, NULL); + else if (is_null_sha1(ref->old_sha1)) + print_ref_status('*', + (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" : + "[new branch]"), + ref, ref->peer_ref, NULL); + else { + char quickref[84]; + char type; + const char *msg; + + strcpy(quickref, status_abbrev(ref->old_sha1)); + if (ref->nonfastforward) { + strcat(quickref, "..."); + type = '+'; + msg = "forced update"; + } else { + strcat(quickref, ".."); + type = ' '; + msg = NULL; + } + strcat(quickref, status_abbrev(ref->new_sha1)); + + print_ref_status(type, quickref, ref, ref->peer_ref, msg); + } +} + +static int print_one_push_status(struct ref *ref, const char *dest, int count) +{ + if (!count) + fprintf(stderr, "To %s\n", dest); + + switch(ref->status) { + case REF_STATUS_NONE: + print_ref_status('X', "[no match]", ref, NULL, NULL); + break; + case REF_STATUS_REJECT_NODELETE: + print_ref_status('!', "[rejected]", ref, NULL, + "remote does not support deleting refs"); + break; + case REF_STATUS_UPTODATE: + print_ref_status('=', "[up to date]", ref, + ref->peer_ref, NULL); + break; + case REF_STATUS_REJECT_NONFASTFORWARD: + print_ref_status('!', "[rejected]", ref, ref->peer_ref, + "non-fast forward"); + break; + case REF_STATUS_REMOTE_REJECT: + print_ref_status('!', "[remote rejected]", ref, + ref->deletion ? NULL : ref->peer_ref, + ref->remote_status); + break; + case REF_STATUS_EXPECTING_REPORT: + print_ref_status('!', "[remote failure]", ref, + ref->deletion ? NULL : ref->peer_ref, + "remote failed to report status"); + break; + case REF_STATUS_OK: + print_ok_ref_status(ref); + break; + } + + return 1; +} + +static void print_push_status(const char *dest, struct ref *refs, int verbose) +{ + struct ref *ref; + int n = 0; + + if (verbose) { + for (ref = refs; ref; ref = ref->next) + if (ref->status == REF_STATUS_UPTODATE) + n += print_one_push_status(ref, dest, n); + } + + for (ref = refs; ref; ref = ref->next) + if (ref->status == REF_STATUS_OK) + n += print_one_push_status(ref, dest, n); + + for (ref = refs; ref; ref = ref->next) { + if (ref->status != REF_STATUS_NONE && + ref->status != REF_STATUS_UPTODATE && + ref->status != REF_STATUS_OK) + n += print_one_push_status(ref, dest, n); + } +} + +static void verify_remote_names(int nr_heads, const char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *local = heads[i]; + const char *remote = strrchr(heads[i], ':'); + + if (*local == '+') + local++; + + /* A matching refspec is okay. */ + if (remote == local && remote[1] == '\0') + continue; + + remote = remote ? (remote + 1) : local; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case CHECK_REF_FORMAT_ONELEVEL: + /* ok but a single level -- that is fine for + * a match pattern. + */ + case CHECK_REF_FORMAT_WILDCARD: + /* ok but ends with a pattern-match character */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} + +static int git_transport_push(struct transport *transport, struct ref *remote_refs, int flags) { struct git_transport_data *data = transport->data; struct send_pack_args args; + int ret; + + if (!data->conn) { + struct ref *tmp_refs; + connect_setup(transport, 1, 0); + + get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL, + NULL); + } - args.receivepack = data->receivepack; - args.send_all = !!(flags & TRANSPORT_PUSH_ALL); args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR); args.force_update = !!(flags & TRANSPORT_PUSH_FORCE); args.use_thin_pack = data->thin; args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE); args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); - return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec); + ret = send_pack(&args, data->fd, data->conn, remote_refs, + &data->extra_have); + + close(data->fd[1]); + close(data->fd[0]); + ret |= finish_connect(data->conn); + data->conn = NULL; + + return ret; } static int disconnect_git(struct transport *transport) @@ -760,7 +960,7 @@ struct transport *transport_get(struct remote *remote, const char *url) ret->set_option = set_git_option; ret->get_refs_list = get_refs_via_connect; ret->fetch = fetch_refs_via_pack; - ret->push = git_transport_push; + ret->push_refs = git_transport_push; ret->disconnect = disconnect_git; data->thin = 1; @@ -787,15 +987,49 @@ int transport_set_option(struct transport *transport, int transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags) { - if (!transport->push) - return 1; - return transport->push(transport, refspec_nr, refspec, flags); + verify_remote_names(refspec_nr, refspec); + + if (transport->push) + return transport->push(transport, refspec_nr, refspec, flags); + if (transport->push_refs) { + struct ref *remote_refs = + transport->get_refs_list(transport, 1); + struct ref *local_refs = get_local_heads(); + int match_flags = MATCH_REFS_NONE; + int verbose = flags & TRANSPORT_PUSH_VERBOSE; + int ret; + + if (flags & TRANSPORT_PUSH_ALL) + match_flags |= MATCH_REFS_ALL; + if (flags & TRANSPORT_PUSH_MIRROR) + match_flags |= MATCH_REFS_MIRROR; + + if (match_refs(local_refs, &remote_refs, + refspec_nr, refspec, match_flags)) { + return -1; + } + + ret = transport->push_refs(transport, remote_refs, flags); + + print_push_status(transport->url, remote_refs, verbose); + + if (!(flags & TRANSPORT_PUSH_DRY_RUN)) { + struct ref *ref; + for (ref = remote_refs; ref; ref = ref->next) + update_tracking_ref(transport->remote, ref, verbose); + } + + if (!ret && !refs_pushed(remote_refs)) + fprintf(stderr, "Everything up-to-date\n"); + return ret; + } + return 1; } const struct ref *transport_get_remote_refs(struct transport *transport) { if (!transport->remote_refs) - transport->remote_refs = transport->get_refs_list(transport); + transport->remote_refs = transport->get_refs_list(transport, 0); return transport->remote_refs; } @@ -822,7 +1056,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs) void transport_unlock_pack(struct transport *transport) { if (transport->pack_lockfile) { - unlink(transport->pack_lockfile); + unlink_or_warn(transport->pack_lockfile); free(transport->pack_lockfile); transport->pack_lockfile = NULL; } @@ -836,3 +1070,51 @@ int transport_disconnect(struct transport *transport) free(transport); return ret; } + +/* + * Strip username (and password) from an url and return + * it in a newly allocated string. + */ +char *transport_anonymize_url(const char *url) +{ + char *anon_url, *scheme_prefix, *anon_part; + size_t anon_len, prefix_len = 0; + + anon_part = strchr(url, '@'); + if (is_local(url) || !anon_part) + goto literal_copy; + + anon_len = strlen(++anon_part); + scheme_prefix = strstr(url, "://"); + if (!scheme_prefix) { + if (!strchr(anon_part, ':')) + /* cannot be "me@there:/path/name" */ + goto literal_copy; + } else { + const char *cp; + /* make sure scheme is reasonable */ + for (cp = url; cp < scheme_prefix; cp++) { + switch (*cp) { + /* RFC 1738 2.1 */ + case '+': case '.': case '-': + break; /* ok */ + default: + if (isalnum(*cp)) + break; + /* it isn't */ + goto literal_copy; + } + } + /* @ past the first slash does not count */ + cp = strchr(scheme_prefix + 3, '/'); + if (cp && cp < anon_part) + goto literal_copy; + prefix_len = scheme_prefix - url + 3; + } + anon_url = xcalloc(1, 1 + prefix_len + anon_len); + memcpy(anon_url, url, prefix_len); + memcpy(anon_url + prefix_len, anon_part, anon_len); + return anon_url; +literal_copy: + return xstrdup(url); +} diff --git a/transport.h b/transport.h index 6bbc1a8264..27bfc528ac 100644 --- a/transport.h +++ b/transport.h @@ -18,8 +18,9 @@ struct transport { int (*set_option)(struct transport *connection, const char *name, const char *value); - struct ref *(*get_refs_list)(struct transport *transport); + struct ref *(*get_refs_list)(struct transport *transport, int for_push); int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs); + int (*push_refs)(struct transport *transport, struct ref *refs, int flags); int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags); int (*disconnect)(struct transport *connection); @@ -73,5 +74,6 @@ const struct ref *transport_get_remote_refs(struct transport *transport); int transport_fetch_refs(struct transport *transport, const struct ref *refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); +char *transport_anonymize_url(const char *url); #endif diff --git a/tree-diff.c b/tree-diff.c index b05d0f4355..edd83949bf 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -374,7 +374,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co } /* - * Then, discard all the non-relevane file pairs... + * Then, discard all the non-relevant file pairs... */ for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; diff --git a/unimplemented.sh b/unimplemented.sh new file mode 100644 index 0000000000..5252de4b25 --- /dev/null +++ b/unimplemented.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +echo >&2 "fatal: git was built without support for `basename $0` (@@REASON@@)." +exit 128 diff --git a/unpack-trees.c b/unpack-trees.c index 5820ce48f3..aaacaf1015 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -7,6 +7,7 @@ #include "unpack-trees.h" #include "progress.h" #include "refs.h" +#include "attr.h" /* * Error messages expected by scripts out of plumbing commands such as @@ -52,36 +53,17 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce, 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 - * directories, in case this unlink is the removal of the - * last entry in the directory -- empty directories are removed. +/* + * Unlink the last component and schedule the leading directories for + * removal, such that empty directories get removed. */ static void unlink_entry(struct cache_entry *ce) { - char *cp, *prev; - char *name = ce->name; - - if (has_symlink_or_noent_leading_path(ce_namelen(ce), ce->name)) + if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return; - if (unlink(name)) + if (unlink_or_warn(ce->name)) return; - prev = NULL; - while (1) { - int status; - cp = strrchr(name, '/'); - if (prev) - *prev = '/'; - if (!cp) - break; - - *cp = 0; - status = rmdir(name); - if (status) { - *cp = '/'; - break; - } - prev = cp; - } + schedule_dir_for_removal(ce->name, ce_namelen(ce)); } static struct checkout state; @@ -105,6 +87,8 @@ static int check_updates(struct unpack_trees_options *o) cnt = 0; } + if (o->update) + git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result); for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; @@ -112,11 +96,10 @@ static int check_updates(struct unpack_trees_options *o) display_progress(progress, ++cnt); if (o->update) unlink_entry(ce); - remove_index_entry_at(&o->result, i); - i--; - continue; } } + remove_marked_cache_entries(&o->result); + remove_scheduled_dirs(); for (i = 0; i < index->cache_nr; i++) { struct cache_entry *ce = index->cache[i]; @@ -130,6 +113,8 @@ static int check_updates(struct unpack_trees_options *o) } } stop_progress(&progress); + if (o->update) + git_attr_set_direction(GIT_ATTR_CHECKIN, NULL); return errs != 0; } @@ -380,8 +365,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options memset(&o->result, 0, sizeof(o->result)); o->result.initialized = 1; - if (o->src_index) - o->result.timestamp = o->src_index->timestamp; + if (o->src_index) { + o->result.timestamp.sec = o->src_index->timestamp.sec; + o->result.timestamp.nsec = o->src_index->timestamp.nsec; + } o->merge_size = len; if (!dfc) @@ -446,7 +433,7 @@ static int verify_uptodate(struct cache_entry *ce, { struct stat st; - if (o->index_only || o->reset) + if (o->index_only || o->reset || ce_uptodate(ce)) return 0; if (!lstat(ce->name, &st)) { @@ -583,7 +570,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_or_noent_leading_path(ce_namelen(ce), ce->name)) + if (has_symlink_or_noent_leading_path(ce->name, ce_namelen(ce))) return 0; if (!lstat(ce->name, &st)) { diff --git a/upload-pack.c b/upload-pack.c index b98b1d61c1..edc7861228 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -66,7 +66,7 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz) } static FILE *pack_pipe = NULL; -static void show_commit(struct commit *commit) +static void show_commit(struct commit *commit, void *data) { if (commit->object.flags & BOUNDARY) fputc('-', pack_pipe); @@ -136,7 +136,7 @@ static int do_rev_list(int fd, void *create_full_pack) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); mark_edges_uninteresting(revs.commits, &revs, show_edge); - traverse_commit_list(&revs, show_commit, show_object); + traverse_commit_list(&revs, show_commit, show_object, NULL); fflush(pack_pipe); fclose(pack_pipe); return 0; @@ -399,12 +399,11 @@ static int get_common_commits(void) static char line[1000]; unsigned char sha1[20]; char hex[41], last_hex[41]; - int len; save_commit_buffer = 0; for(;;) { - len = packet_read_line(0, line, sizeof(line)); + int len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) { @@ -412,7 +411,7 @@ static int get_common_commits(void) packet_write(1, "NAK\n"); continue; } - len = strip(line, len); + strip(line, len); if (!prefixcmp(line, "have ")) { switch (got_sha1(line+5, sha1)) { case -1: /* they have what we do not */ @@ -647,7 +646,7 @@ int main(int argc, char **argv) dir = argv[i]; if (!enter_repo(dir, strict)) - die("'%s': unable to chdir or not a git archive", dir); + die("'%s' does not appear to be a git repository", dir); if (is_repository_shallow()) die("attempt to fetch/clone from a shallow repository"); if (getenv("GIT_DEBUG_SEND_PACK")) @@ -354,7 +354,7 @@ int is_encoding_utf8(const char *name) * with iconv. If the conversion fails, returns NULL. */ #ifndef NO_ICONV -#ifdef OLD_ICONV +#if defined(OLD_ICONV) || (defined(__sun__) && !defined(_XPG6)) typedef const char * iconv_ibp; #else typedef char * iconv_ibp; @@ -289,3 +289,19 @@ int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1) safe_create_leading_directories(name); return open(name, O_RDWR|O_CREAT|O_EXCL, 0600); } + +int unlink_or_warn(const char *file) +{ + int rc = unlink(file); + + if (rc < 0) { + int err = errno; + if (ENOENT != err) { + warning("unable to unlink %s: %s", + file, strerror(errno)); + errno = err; + } + } + return rc; +} + diff --git a/wt-status.c b/wt-status.c index 96ff2f8f56..1b6df45450 100644 --- a/wt-status.c +++ b/wt-status.c @@ -15,11 +15,11 @@ int wt_status_relative_paths = 1; int wt_status_use_color = -1; int wt_status_submodule_summary; static char wt_status_colors[][COLOR_MAXLEN] = { - "", /* WT_STATUS_HEADER: normal */ - "\033[32m", /* WT_STATUS_UPDATED: green */ - "\033[31m", /* WT_STATUS_CHANGED: red */ - "\033[31m", /* WT_STATUS_UNTRACKED: red */ - "\033[31m", /* WT_STATUS_NOBRANCH: red */ + GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */ + GIT_COLOR_GREEN, /* WT_STATUS_UPDATED */ + GIT_COLOR_RED, /* WT_STATUS_CHANGED */ + GIT_COLOR_RED, /* WT_STATUS_UNTRACKED */ + GIT_COLOR_RED, /* WT_STATUS_NOBRANCH */ }; enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; @@ -40,7 +40,7 @@ static int parse_status_slot(const char *var, int offset) die("bad config variable '%s'", var); } -static const char* color(int slot) +static const char *color(int slot) { return wt_status_use_color > 0 ? wt_status_colors[slot] : ""; } @@ -250,10 +250,9 @@ static void wt_status_print_untracked(struct wt_status *s) memset(&dir, 0, sizeof(dir)); - if (!s->untracked) { - dir.show_other_directories = 1; - dir.hide_empty_directories = 1; - } + if (!s->untracked) + dir.flags |= + DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES; setup_standard_excludes(&dir); read_directory(&dir, ".", "", 0, NULL); diff --git a/xdiff-interface.c b/xdiff-interface.c index d782f06d99..b9b0db8d86 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c @@ -15,11 +15,10 @@ static int parse_num(char **cp_p, int *num_p) { char *cp = *cp_p; int num = 0; - int read_some; while ('0' <= *cp && *cp <= '9') num = num * 10 + *cp++ - '0'; - if (!(read_some = cp - *cp_p)) + if (!(cp - *cp_p)) return -1; *cp_p = cp; *num_p = num; diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 3e97462bdd..1ebab687f7 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -293,15 +293,14 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, for (; off1 < lim1; off1++) rchg1[rindex1[off1]] = 1; } else { - long ec; xdpsplit_t spl; spl.i1 = spl.i2 = 0; /* * Divide ... */ - if ((ec = xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, - need_min, &spl, xenv)) < 0) { + if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb, + need_min, &spl, xenv) < 0) { return -1; } @@ -457,7 +456,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { /* * Record the end-of-group position in case we are matched * with a group of changes in the other file (that is, the - * change record before the enf-of-group index in the other + * change record before the end-of-group index in the other * file is set). */ ixref = rchgo[ixo - 1] ? ix: nrec; diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 05bfa41f10..c4bedf0d1c 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -132,7 +132,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, if (xecfg->flags & XDL_EMIT_COMMON) return xdl_emit_common(xe, xscr, ecb, xecfg); - for (xch = xche = xscr; xch; xch = xche->next) { + for (xch = xscr; xch; xch = xche->next) { xche = xdl_get_hunk(xch, xecfg); s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0); diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index d9737f04c2..1cb65a9516 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -563,23 +563,22 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, return -1; } status = 0; - if (xscr1 || xscr2) { - if (!xscr1) { - result->ptr = xdl_malloc(mf2->size); - memcpy(result->ptr, mf2->ptr, mf2->size); - result->size = mf2->size; - } else if (!xscr2) { - result->ptr = xdl_malloc(mf1->size); - memcpy(result->ptr, mf1->ptr, mf1->size); - result->size = mf1->size; - } else { - status = xdl_do_merge(&xe1, xscr1, name1, - &xe2, xscr2, name2, - flags, xpp, result); - } - xdl_free_script(xscr1); - xdl_free_script(xscr2); + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + flags, xpp, result); } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + xdl_free_env(&xe1); xdl_free_env(&xe2); |